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 { FirstLoadService, UserService } from "../../services";
33 import { HttpService, RequestState } from "../../services/HttpService";
34 import { I18NextService, languages } from "../../services/I18NextService";
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 {I18NextService.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 FirstLoadService.isFirstLoad;
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 I18NextService.i18n.t("settings");
243 <div className="person-settings 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: I18NextService.i18n.t("settings"),
255 getNode: this.userSettings,
259 label: I18NextService.i18n.t("blocks"),
260 getNode: this.blockCards,
268 userSettings(isSelected) {
271 className={classNames("tab-pane show", {
275 id="settings-tab-pane"
277 <div className="row">
278 <div className="col-12 col-md-6">
279 <div className="card border-secondary mb-3">
280 <div className="card-body">{this.saveUserSettingsHtmlForm()}</div>
283 <div className="col-12 col-md-6">
284 <div className="card border-secondary mb-3">
285 <div className="card-body">{this.changePasswordHtmlForm()}</div>
293 blockCards(isSelected) {
296 className={classNames("tab-pane", {
302 <div className="row">
303 <div className="col-12 col-md-6">
304 <div className="card border-secondary mb-3">
305 <div className="card-body">{this.blockUserCard()}</div>
308 <div className="col-12 col-md-6">
309 <div className="card border-secondary mb-3">
310 <div className="card-body">{this.blockCommunityCard()}</div>
318 changePasswordHtmlForm() {
321 <h5>{I18NextService.i18n.t("change_password")}</h5>
322 <form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
323 <div className="mb-3 row">
324 <label className="col-sm-5 col-form-label" htmlFor="user-password">
325 {I18NextService.i18n.t("new_password")}
327 <div className="col-sm-7">
331 className="form-control"
332 value={this.state.changePasswordForm.new_password}
333 autoComplete="new-password"
335 onInput={linkEvent(this, this.handleNewPasswordChange)}
339 <div className="mb-3 row">
341 className="col-sm-5 col-form-label"
342 htmlFor="user-verify-password"
344 {I18NextService.i18n.t("verify_password")}
346 <div className="col-sm-7">
349 id="user-verify-password"
350 className="form-control"
351 value={this.state.changePasswordForm.new_password_verify}
352 autoComplete="new-password"
354 onInput={linkEvent(this, this.handleNewPasswordVerifyChange)}
358 <div className="mb-3 row">
360 className="col-sm-5 col-form-label"
361 htmlFor="user-old-password"
363 {I18NextService.i18n.t("old_password")}
365 <div className="col-sm-7">
368 id="user-old-password"
369 className="form-control"
370 value={this.state.changePasswordForm.old_password}
371 autoComplete="new-password"
373 onInput={linkEvent(this, this.handleOldPasswordChange)}
377 <div className="input-group mb-3">
380 className="btn d-block btn-secondary me-4 w-100"
382 {this.state.changePasswordRes.state === "loading" ? (
385 capitalizeFirstLetter(I18NextService.i18n.t("save"))
395 const { searchPersonLoading, searchPersonOptions } = this.state;
401 loading={searchPersonLoading}
402 onChange={this.handleBlockPerson}
403 onSearch={this.handlePersonSearch}
404 options={searchPersonOptions}
406 {this.blockedUsersList()}
414 <h5>{I18NextService.i18n.t("blocked_users")}</h5>
415 <ul className="list-unstyled mb-0">
416 {this.state.personBlocks.map(pb => (
417 <li key={pb.target.id}>
419 <PersonListing person={pb.target} />
421 className="btn btn-sm"
423 { ctx: this, recipientId: pb.target.id },
424 this.handleUnblockPerson
426 data-tippy-content={I18NextService.i18n.t("unblock_user")}
428 <Icon icon="x" classes="icon-inline" />
438 blockCommunityCard() {
439 const { searchCommunityLoading, searchCommunityOptions } = this.state;
444 filterType="community"
445 loading={searchCommunityLoading}
446 onChange={this.handleBlockCommunity}
447 onSearch={this.handleCommunitySearch}
448 options={searchCommunityOptions}
450 {this.blockedCommunitiesList()}
455 blockedCommunitiesList() {
458 <h5>{I18NextService.i18n.t("blocked_communities")}</h5>
459 <ul className="list-unstyled mb-0">
460 {this.state.communityBlocks.map(cb => (
461 <li key={cb.community.id}>
463 <CommunityLink community={cb.community} />
465 className="btn btn-sm"
467 { ctx: this, communityId: cb.community.id },
468 this.handleUnblockCommunity
470 data-tippy-content={I18NextService.i18n.t(
474 <Icon icon="x" classes="icon-inline" />
484 saveUserSettingsHtmlForm() {
485 const selectedLangs = this.state.saveUserSettingsForm.discussion_languages;
489 <h5>{I18NextService.i18n.t("settings")}</h5>
490 <form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}>
491 <div className="mb-3 row">
492 <label className="col-sm-3 col-form-label" htmlFor="display-name">
493 {I18NextService.i18n.t("display_name")}
495 <div className="col-sm-9">
499 className="form-control"
500 placeholder={I18NextService.i18n.t("optional")}
501 value={this.state.saveUserSettingsForm.display_name}
502 onInput={linkEvent(this, this.handleDisplayNameChange)}
503 pattern="^(?!@)(.+)$"
508 <div className="mb-3 row">
509 <label className="col-sm-3 col-form-label" htmlFor="user-bio">
510 {I18NextService.i18n.t("bio")}
512 <div className="col-sm-9">
514 initialContent={this.state.saveUserSettingsForm.bio}
515 onContentChange={this.handleBioChange}
517 hideNavigationWarnings
518 allLanguages={this.state.siteRes.all_languages}
519 siteLanguages={this.state.siteRes.discussion_languages}
523 <div className="mb-3 row">
524 <label className="col-sm-3 col-form-label" htmlFor="user-email">
525 {I18NextService.i18n.t("email")}
527 <div className="col-sm-9">
531 className="form-control"
532 placeholder={I18NextService.i18n.t("optional")}
533 value={this.state.saveUserSettingsForm.email}
534 onInput={linkEvent(this, this.handleEmailChange)}
539 <div className="mb-3 row">
540 <label className="col-sm-3 col-form-label" htmlFor="matrix-user-id">
541 <a href={elementUrl} rel={relTags}>
542 {I18NextService.i18n.t("matrix_user_id")}
545 <div className="col-sm-9">
549 className="form-control"
550 placeholder="@user:example.com"
551 value={this.state.saveUserSettingsForm.matrix_user_id}
552 onInput={linkEvent(this, this.handleMatrixUserIdChange)}
553 pattern="^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
557 <div className="mb-3 row">
558 <label className="col-sm-3 col-form-label">
559 {I18NextService.i18n.t("avatar")}
561 <div className="col-sm-9">
563 uploadTitle={I18NextService.i18n.t("upload_avatar")}
564 imageSrc={this.state.saveUserSettingsForm.avatar}
565 onUpload={this.handleAvatarUpload}
566 onRemove={this.handleAvatarRemove}
571 <div className="mb-3 row">
572 <label className="col-sm-3 col-form-label">
573 {I18NextService.i18n.t("banner")}
575 <div className="col-sm-9">
577 uploadTitle={I18NextService.i18n.t("upload_banner")}
578 imageSrc={this.state.saveUserSettingsForm.banner}
579 onUpload={this.handleBannerUpload}
580 onRemove={this.handleBannerRemove}
584 <div className="mb-3 row">
585 <label className="col-sm-3 form-label" htmlFor="user-language">
586 {I18NextService.i18n.t("interface_language")}
588 <div className="col-sm-9">
591 value={this.state.saveUserSettingsForm.interface_language}
592 onChange={linkEvent(this, this.handleInterfaceLangChange)}
593 className="form-select d-inline-block w-auto"
595 <option disabled aria-hidden="true">
596 {I18NextService.i18n.t("interface_language")}
598 <option value="browser">
599 {I18NextService.i18n.t("browser_default")}
601 <option disabled aria-hidden="true">
605 .sort((a, b) => a.code.localeCompare(b.code))
607 <option key={lang.code} value={lang.code}>
615 allLanguages={this.state.siteRes.all_languages}
616 siteLanguages={this.state.siteRes.discussion_languages}
617 selectedLanguageIds={selectedLangs}
619 showLanguageWarning={true}
621 onChange={this.handleDiscussionLanguageChange}
623 <div className="mb-3 row">
624 <label className="col-sm-3 col-form-label" htmlFor="user-theme">
625 {I18NextService.i18n.t("theme")}
627 <div className="col-sm-9">
630 value={this.state.saveUserSettingsForm.theme}
631 onChange={linkEvent(this, this.handleThemeChange)}
632 className="form-select d-inline-block w-auto"
634 <option disabled aria-hidden="true">
635 {I18NextService.i18n.t("theme")}
637 <option value="browser">
638 {I18NextService.i18n.t("browser_default")}
640 {this.state.themeList.map(theme => (
641 <option key={theme} value={theme}>
648 <form className="mb-3 row">
649 <label className="col-sm-3 col-form-label">
650 {I18NextService.i18n.t("type")}
652 <div className="col-sm-9">
655 this.state.saveUserSettingsForm.default_listing_type ??
658 showLocal={showLocal(this.isoData)}
660 onChange={this.handleListingTypeChange}
664 <form className="mb-3 row">
665 <label className="col-sm-3 col-form-label">
666 {I18NextService.i18n.t("sort_type")}
668 <div className="col-sm-9">
671 this.state.saveUserSettingsForm.default_sort_type ?? "Active"
673 onChange={this.handleSortTypeChange}
677 <div className="input-group mb-3">
678 <div className="form-check">
680 className="form-check-input"
683 checked={this.state.saveUserSettingsForm.show_nsfw}
684 onChange={linkEvent(this, this.handleShowNsfwChange)}
686 <label className="form-check-label" htmlFor="user-show-nsfw">
687 {I18NextService.i18n.t("show_nsfw")}
691 <div className="input-group mb-3">
692 <div className="form-check">
694 className="form-check-input"
695 id="user-show-scores"
697 checked={this.state.saveUserSettingsForm.show_scores}
698 onChange={linkEvent(this, this.handleShowScoresChange)}
700 <label className="form-check-label" htmlFor="user-show-scores">
701 {I18NextService.i18n.t("show_scores")}
705 <div className="input-group mb-3">
706 <div className="form-check">
708 className="form-check-input"
709 id="user-show-avatars"
711 checked={this.state.saveUserSettingsForm.show_avatars}
712 onChange={linkEvent(this, this.handleShowAvatarsChange)}
714 <label className="form-check-label" htmlFor="user-show-avatars">
715 {I18NextService.i18n.t("show_avatars")}
719 <div className="input-group mb-3">
720 <div className="form-check">
722 className="form-check-input"
723 id="user-bot-account"
725 checked={this.state.saveUserSettingsForm.bot_account}
726 onChange={linkEvent(this, this.handleBotAccount)}
728 <label className="form-check-label" htmlFor="user-bot-account">
729 {I18NextService.i18n.t("bot_account")}
733 <div className="input-group mb-3">
734 <div className="form-check">
736 className="form-check-input"
737 id="user-show-bot-accounts"
739 checked={this.state.saveUserSettingsForm.show_bot_accounts}
740 onChange={linkEvent(this, this.handleShowBotAccounts)}
743 className="form-check-label"
744 htmlFor="user-show-bot-accounts"
746 {I18NextService.i18n.t("show_bot_accounts")}
750 <div className="input-group mb-3">
751 <div className="form-check">
753 className="form-check-input"
754 id="user-show-read-posts"
756 checked={this.state.saveUserSettingsForm.show_read_posts}
757 onChange={linkEvent(this, this.handleReadPosts)}
760 className="form-check-label"
761 htmlFor="user-show-read-posts"
763 {I18NextService.i18n.t("show_read_posts")}
767 <div className="input-group mb-3">
768 <div className="form-check">
770 className="form-check-input"
771 id="user-show-new-post-notifs"
773 checked={this.state.saveUserSettingsForm.show_new_post_notifs}
774 onChange={linkEvent(this, this.handleShowNewPostNotifs)}
777 className="form-check-label"
778 htmlFor="user-show-new-post-notifs"
780 {I18NextService.i18n.t("show_new_post_notifs")}
784 <div className="input-group mb-3">
785 <div className="form-check">
787 className="form-check-input"
788 id="user-send-notifications-to-email"
790 disabled={!this.state.saveUserSettingsForm.email}
792 this.state.saveUserSettingsForm.send_notifications_to_email
796 this.handleSendNotificationsToEmailChange
800 className="form-check-label"
801 htmlFor="user-send-notifications-to-email"
803 {I18NextService.i18n.t("send_notifications_to_email")}
808 <div className="input-group mb-3">
809 <button type="submit" className="btn d-block btn-secondary me-4">
810 {this.state.saveRes.state === "loading" ? (
813 capitalizeFirstLetter(I18NextService.i18n.t("save"))
818 <div className="input-group mb-3">
820 className="btn d-block btn-danger"
823 this.handleDeleteAccountShowConfirmToggle
826 {I18NextService.i18n.t("delete_account")}
828 {this.state.deleteAccountShowConfirm && (
830 <div className="my-2 alert alert-danger" role="alert">
831 {I18NextService.i18n.t("delete_account_confirm")}
835 value={this.state.deleteAccountForm.password}
836 autoComplete="new-password"
840 this.handleDeleteAccountPasswordChange
842 className="form-control my-2"
845 className="btn btn-danger me-4"
846 disabled={!this.state.deleteAccountForm.password}
847 onClick={linkEvent(this, this.handleDeleteAccount)}
849 {this.state.deleteAccountRes.state === "loading" ? (
852 capitalizeFirstLetter(I18NextService.i18n.t("delete"))
856 className="btn btn-secondary"
859 this.handleDeleteAccountShowConfirmToggle
862 {I18NextService.i18n.t("cancel")}
874 UserService.Instance.myUserInfo?.local_user_view.local_user.totp_2fa_url;
879 <div className="input-group mb-3">
880 <div className="form-check">
882 className="form-check-input"
883 id="user-generate-totp"
885 checked={this.state.saveUserSettingsForm.generate_totp_2fa}
886 onChange={linkEvent(this, this.handleGenerateTotp)}
888 <label className="form-check-label" htmlFor="user-generate-totp">
889 {I18NextService.i18n.t("set_up_two_factor")}
898 <a className="btn btn-secondary mb-2" href={totpUrl}>
899 {I18NextService.i18n.t("two_factor_link")}
902 <div className="input-group mb-3">
903 <div className="form-check">
905 className="form-check-input"
906 id="user-remove-totp"
909 this.state.saveUserSettingsForm.generate_totp_2fa == false
911 onChange={linkEvent(this, this.handleRemoveTotp)}
913 <label className="form-check-label" htmlFor="user-remove-totp">
914 {I18NextService.i18n.t("remove_two_factor")}
924 handlePersonSearch = debounce(async (text: string) => {
925 this.setState({ searchPersonLoading: true });
927 const searchPersonOptions: Choice[] = [];
929 if (text.length > 0) {
930 searchPersonOptions.push(...(await fetchUsers(text)).map(personToChoice));
934 searchPersonLoading: false,
939 handleCommunitySearch = debounce(async (text: string) => {
940 this.setState({ searchCommunityLoading: true });
942 const searchCommunityOptions: Choice[] = [];
944 if (text.length > 0) {
945 searchCommunityOptions.push(
946 ...(await fetchCommunities(text)).map(communityToChoice)
951 searchCommunityLoading: false,
952 searchCommunityOptions,
956 async handleBlockPerson({ value }: Choice) {
958 const res = await HttpService.client.blockPerson({
959 person_id: Number(value),
961 auth: myAuthRequired(),
963 this.personBlock(res);
967 async handleUnblockPerson({
974 const res = await HttpService.client.blockPerson({
975 person_id: recipientId,
977 auth: myAuthRequired(),
979 ctx.personBlock(res);
982 async handleBlockCommunity({ value }: Choice) {
984 const res = await HttpService.client.blockCommunity({
985 community_id: Number(value),
987 auth: myAuthRequired(),
989 this.communityBlock(res);
993 async handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
994 const auth = myAuth();
996 const res = await HttpService.client.blockCommunity({
997 community_id: i.communityId,
999 auth: myAuthRequired(),
1001 i.ctx.communityBlock(res);
1005 handleShowNsfwChange(i: Settings, event: any) {
1007 s => ((s.saveUserSettingsForm.show_nsfw = event.target.checked), s)
1011 handleShowAvatarsChange(i: Settings, event: any) {
1012 const mui = UserService.Instance.myUserInfo;
1014 mui.local_user_view.local_user.show_avatars = event.target.checked;
1017 s => ((s.saveUserSettingsForm.show_avatars = event.target.checked), s)
1021 handleBotAccount(i: Settings, event: any) {
1023 s => ((s.saveUserSettingsForm.bot_account = event.target.checked), s)
1027 handleShowBotAccounts(i: Settings, event: any) {
1030 (s.saveUserSettingsForm.show_bot_accounts = event.target.checked), s
1035 handleReadPosts(i: Settings, event: any) {
1037 s => ((s.saveUserSettingsForm.show_read_posts = event.target.checked), s)
1041 handleShowNewPostNotifs(i: Settings, event: any) {
1044 (s.saveUserSettingsForm.show_new_post_notifs = event.target.checked), s
1049 handleShowScoresChange(i: Settings, event: any) {
1050 const mui = UserService.Instance.myUserInfo;
1052 mui.local_user_view.local_user.show_scores = event.target.checked;
1055 s => ((s.saveUserSettingsForm.show_scores = event.target.checked), s)
1059 handleGenerateTotp(i: Settings, event: any) {
1060 // Coerce false to undefined here, so it won't generate it.
1061 const checked: boolean | undefined = event.target.checked || undefined;
1063 toast(I18NextService.i18n.t("two_factor_setup_instructions"));
1065 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1068 handleRemoveTotp(i: Settings, event: any) {
1069 // Coerce true to undefined here, so it won't generate it.
1070 const checked: boolean | undefined = !event.target.checked && undefined;
1071 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1074 handleSendNotificationsToEmailChange(i: Settings, event: any) {
1077 (s.saveUserSettingsForm.send_notifications_to_email =
1078 event.target.checked),
1084 handleThemeChange(i: Settings, event: any) {
1085 i.setState(s => ((s.saveUserSettingsForm.theme = event.target.value), s));
1086 setTheme(event.target.value, true);
1089 handleInterfaceLangChange(i: Settings, event: any) {
1090 const newLang = event.target.value ?? "browser";
1091 I18NextService.i18n.changeLanguage(
1092 newLang === "browser" ? navigator.languages : newLang
1096 s => ((s.saveUserSettingsForm.interface_language = event.target.value), s)
1100 handleDiscussionLanguageChange(val: number[]) {
1102 s => ((s.saveUserSettingsForm.discussion_languages = val), s)
1106 handleSortTypeChange(val: SortType) {
1107 this.setState(s => ((s.saveUserSettingsForm.default_sort_type = val), s));
1110 handleListingTypeChange(val: ListingType) {
1112 s => ((s.saveUserSettingsForm.default_listing_type = val), s)
1116 handleEmailChange(i: Settings, event: any) {
1117 i.setState(s => ((s.saveUserSettingsForm.email = event.target.value), s));
1120 handleBioChange(val: string) {
1121 this.setState(s => ((s.saveUserSettingsForm.bio = val), s));
1124 handleAvatarUpload(url: string) {
1125 this.setState(s => ((s.saveUserSettingsForm.avatar = url), s));
1128 handleAvatarRemove() {
1129 this.setState(s => ((s.saveUserSettingsForm.avatar = ""), s));
1132 handleBannerUpload(url: string) {
1133 this.setState(s => ((s.saveUserSettingsForm.banner = url), s));
1136 handleBannerRemove() {
1137 this.setState(s => ((s.saveUserSettingsForm.banner = ""), s));
1140 handleDisplayNameChange(i: Settings, event: any) {
1142 s => ((s.saveUserSettingsForm.display_name = event.target.value), s)
1146 handleMatrixUserIdChange(i: Settings, event: any) {
1148 s => ((s.saveUserSettingsForm.matrix_user_id = event.target.value), s)
1152 handleNewPasswordChange(i: Settings, event: any) {
1153 const newPass: string | undefined =
1154 event.target.value == "" ? undefined : event.target.value;
1155 i.setState(s => ((s.changePasswordForm.new_password = newPass), s));
1158 handleNewPasswordVerifyChange(i: Settings, event: any) {
1159 const newPassVerify: string | undefined =
1160 event.target.value == "" ? undefined : event.target.value;
1162 s => ((s.changePasswordForm.new_password_verify = newPassVerify), s)
1166 handleOldPasswordChange(i: Settings, event: any) {
1167 const oldPass: string | undefined =
1168 event.target.value == "" ? undefined : event.target.value;
1169 i.setState(s => ((s.changePasswordForm.old_password = oldPass), s));
1172 async handleSaveSettingsSubmit(i: Settings, event: any) {
1173 event.preventDefault();
1174 i.setState({ saveRes: { state: "loading" } });
1176 const saveRes = await HttpService.client.saveUserSettings({
1177 ...i.state.saveUserSettingsForm,
1178 auth: myAuthRequired(),
1180 if (saveRes.state === "success") {
1181 UserService.Instance.login(saveRes.data);
1182 toast(I18NextService.i18n.t("saved"));
1183 window.scrollTo(0, 0);
1186 i.setState({ saveRes });
1189 async handleChangePasswordSubmit(i: Settings, event: any) {
1190 event.preventDefault();
1191 const { new_password, new_password_verify, old_password } =
1192 i.state.changePasswordForm;
1194 if (new_password && old_password && new_password_verify) {
1195 i.setState({ changePasswordRes: { state: "loading" } });
1196 const changePasswordRes = await HttpService.client.changePassword({
1198 new_password_verify,
1200 auth: myAuthRequired(),
1202 if (changePasswordRes.state === "success") {
1203 UserService.Instance.login(changePasswordRes.data);
1204 window.scrollTo(0, 0);
1205 toast(I18NextService.i18n.t("password_changed"));
1208 i.setState({ changePasswordRes });
1212 handleDeleteAccountShowConfirmToggle(i: Settings) {
1213 i.setState({ deleteAccountShowConfirm: !i.state.deleteAccountShowConfirm });
1216 handleDeleteAccountPasswordChange(i: Settings, event: any) {
1217 i.setState(s => ((s.deleteAccountForm.password = event.target.value), s));
1220 async handleDeleteAccount(i: Settings) {
1221 const password = i.state.deleteAccountForm.password;
1223 i.setState({ deleteAccountRes: { state: "loading" } });
1224 const deleteAccountRes = await HttpService.client.deleteAccount({
1226 auth: myAuthRequired(),
1228 if (deleteAccountRes.state === "success") {
1229 UserService.Instance.logout();
1230 this.context.router.history.replace("/");
1233 i.setState({ deleteAccountRes });
1237 handleSwitchTab(i: { ctx: Settings; tab: string }) {
1238 i.ctx.setState({ currentTab: i.tab });
1241 personBlock(res: RequestState<BlockPersonResponse>) {
1242 if (res.state === "success") {
1243 updatePersonBlock(res.data);
1244 const mui = UserService.Instance.myUserInfo;
1246 this.setState({ personBlocks: mui.person_blocks });
1251 communityBlock(res: RequestState<BlockCommunityResponse>) {
1252 if (res.state === "success") {
1253 updateCommunityBlock(res.data);
1254 const mui = UserService.Instance.myUserInfo;
1256 this.setState({ communityBlocks: mui.community_blocks });