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 { 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 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 I18NextService.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: I18NextService.i18n.t("settings"),
253 getNode: this.userSettings,
257 label: I18NextService.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 <h2 className="h5">{I18NextService.i18n.t("change_password")}</h2>
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 {I18NextService.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 {I18NextService.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 {I18NextService.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(I18NextService.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 <h2 className="h5">{I18NextService.i18n.t("blocked_users")}</h2>
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={I18NextService.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 <h2 className="h5">{I18NextService.i18n.t("blocked_communities")}</h2>
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={I18NextService.i18n.t(
472 <Icon icon="x" classes="icon-inline" />
482 saveUserSettingsHtmlForm() {
483 const selectedLangs = this.state.saveUserSettingsForm.discussion_languages;
487 <h2 className="h5">{I18NextService.i18n.t("settings")}</h2>
488 <form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}>
489 <div className="mb-3 row">
490 <label className="col-sm-3 col-form-label" htmlFor="display-name">
491 {I18NextService.i18n.t("display_name")}
493 <div className="col-sm-9">
497 className="form-control"
498 placeholder={I18NextService.i18n.t("optional")}
499 value={this.state.saveUserSettingsForm.display_name}
500 onInput={linkEvent(this, this.handleDisplayNameChange)}
501 pattern="^(?!@)(.+)$"
506 <div className="mb-3 row">
507 <label className="col-sm-3 col-form-label" htmlFor="user-bio">
508 {I18NextService.i18n.t("bio")}
510 <div className="col-sm-9">
512 initialContent={this.state.saveUserSettingsForm.bio}
513 onContentChange={this.handleBioChange}
515 hideNavigationWarnings
516 allLanguages={this.state.siteRes.all_languages}
517 siteLanguages={this.state.siteRes.discussion_languages}
521 <div className="mb-3 row">
522 <label className="col-sm-3 col-form-label" htmlFor="user-email">
523 {I18NextService.i18n.t("email")}
525 <div className="col-sm-9">
529 className="form-control"
530 placeholder={I18NextService.i18n.t("optional")}
531 value={this.state.saveUserSettingsForm.email}
532 onInput={linkEvent(this, this.handleEmailChange)}
537 <div className="mb-3 row">
538 <label className="col-sm-3 col-form-label" htmlFor="matrix-user-id">
539 <a href={elementUrl} rel={relTags}>
540 {I18NextService.i18n.t("matrix_user_id")}
543 <div className="col-sm-9">
547 className="form-control"
548 placeholder="@user:example.com"
549 value={this.state.saveUserSettingsForm.matrix_user_id}
550 onInput={linkEvent(this, this.handleMatrixUserIdChange)}
551 pattern="^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
555 <div className="mb-3 row">
556 <label className="col-sm-3 col-form-label">
557 {I18NextService.i18n.t("avatar")}
559 <div className="col-sm-9">
561 uploadTitle={I18NextService.i18n.t("upload_avatar")}
562 imageSrc={this.state.saveUserSettingsForm.avatar}
563 onUpload={this.handleAvatarUpload}
564 onRemove={this.handleAvatarRemove}
569 <div className="mb-3 row">
570 <label className="col-sm-3 col-form-label">
571 {I18NextService.i18n.t("banner")}
573 <div className="col-sm-9">
575 uploadTitle={I18NextService.i18n.t("upload_banner")}
576 imageSrc={this.state.saveUserSettingsForm.banner}
577 onUpload={this.handleBannerUpload}
578 onRemove={this.handleBannerRemove}
582 <div className="mb-3 row">
583 <label className="col-sm-3 form-label" htmlFor="user-language">
584 {I18NextService.i18n.t("interface_language")}
586 <div className="col-sm-9">
589 value={this.state.saveUserSettingsForm.interface_language}
590 onChange={linkEvent(this, this.handleInterfaceLangChange)}
591 className="form-select d-inline-block w-auto"
593 <option disabled aria-hidden="true">
594 {I18NextService.i18n.t("interface_language")}
596 <option value="browser">
597 {I18NextService.i18n.t("browser_default")}
599 <option disabled aria-hidden="true">
603 .sort((a, b) => a.code.localeCompare(b.code))
605 <option key={lang.code} value={lang.code}>
613 allLanguages={this.state.siteRes.all_languages}
614 siteLanguages={this.state.siteRes.discussion_languages}
615 selectedLanguageIds={selectedLangs}
617 showLanguageWarning={true}
619 onChange={this.handleDiscussionLanguageChange}
621 <div className="mb-3 row">
622 <label className="col-sm-3 col-form-label" htmlFor="user-theme">
623 {I18NextService.i18n.t("theme")}
625 <div className="col-sm-9">
628 value={this.state.saveUserSettingsForm.theme}
629 onChange={linkEvent(this, this.handleThemeChange)}
630 className="form-select d-inline-block w-auto"
632 <option disabled aria-hidden="true">
633 {I18NextService.i18n.t("theme")}
635 <option value="browser">
636 {I18NextService.i18n.t("browser_default")}
638 <option value="browser-compact">
639 {I18NextService.i18n.t("browser_default_compact")}
641 {this.state.themeList.map(theme => (
642 <option key={theme} value={theme}>
649 <form className="mb-3 row">
650 <label className="col-sm-3 col-form-label">
651 {I18NextService.i18n.t("type")}
653 <div className="col-sm-9">
656 this.state.saveUserSettingsForm.default_listing_type ??
659 showLocal={showLocal(this.isoData)}
661 onChange={this.handleListingTypeChange}
665 <form className="mb-3 row">
666 <label className="col-sm-3 col-form-label">
667 {I18NextService.i18n.t("sort_type")}
669 <div className="col-sm-9">
672 this.state.saveUserSettingsForm.default_sort_type ?? "Active"
674 onChange={this.handleSortTypeChange}
678 <div className="input-group mb-3">
679 <div className="form-check">
681 className="form-check-input"
684 checked={this.state.saveUserSettingsForm.show_nsfw}
685 onChange={linkEvent(this, this.handleShowNsfwChange)}
687 <label className="form-check-label" htmlFor="user-show-nsfw">
688 {I18NextService.i18n.t("show_nsfw")}
692 <div className="input-group mb-3">
693 <div className="form-check">
695 className="form-check-input"
696 id="user-show-scores"
698 checked={this.state.saveUserSettingsForm.show_scores}
699 onChange={linkEvent(this, this.handleShowScoresChange)}
701 <label className="form-check-label" htmlFor="user-show-scores">
702 {I18NextService.i18n.t("show_scores")}
706 <div className="input-group mb-3">
707 <div className="form-check">
709 className="form-check-input"
710 id="user-show-avatars"
712 checked={this.state.saveUserSettingsForm.show_avatars}
713 onChange={linkEvent(this, this.handleShowAvatarsChange)}
715 <label className="form-check-label" htmlFor="user-show-avatars">
716 {I18NextService.i18n.t("show_avatars")}
720 <div className="input-group mb-3">
721 <div className="form-check">
723 className="form-check-input"
724 id="user-bot-account"
726 checked={this.state.saveUserSettingsForm.bot_account}
727 onChange={linkEvent(this, this.handleBotAccount)}
729 <label className="form-check-label" htmlFor="user-bot-account">
730 {I18NextService.i18n.t("bot_account")}
734 <div className="input-group mb-3">
735 <div className="form-check">
737 className="form-check-input"
738 id="user-show-bot-accounts"
740 checked={this.state.saveUserSettingsForm.show_bot_accounts}
741 onChange={linkEvent(this, this.handleShowBotAccounts)}
744 className="form-check-label"
745 htmlFor="user-show-bot-accounts"
747 {I18NextService.i18n.t("show_bot_accounts")}
751 <div className="input-group mb-3">
752 <div className="form-check">
754 className="form-check-input"
755 id="user-show-read-posts"
757 checked={this.state.saveUserSettingsForm.show_read_posts}
758 onChange={linkEvent(this, this.handleReadPosts)}
761 className="form-check-label"
762 htmlFor="user-show-read-posts"
764 {I18NextService.i18n.t("show_read_posts")}
768 <div className="input-group mb-3">
769 <div className="form-check">
771 className="form-check-input"
772 id="user-show-new-post-notifs"
774 checked={this.state.saveUserSettingsForm.show_new_post_notifs}
775 onChange={linkEvent(this, this.handleShowNewPostNotifs)}
778 className="form-check-label"
779 htmlFor="user-show-new-post-notifs"
781 {I18NextService.i18n.t("show_new_post_notifs")}
785 <div className="input-group mb-3">
786 <div className="form-check">
788 className="form-check-input"
789 id="user-send-notifications-to-email"
791 disabled={!this.state.saveUserSettingsForm.email}
793 this.state.saveUserSettingsForm.send_notifications_to_email
797 this.handleSendNotificationsToEmailChange
801 className="form-check-label"
802 htmlFor="user-send-notifications-to-email"
804 {I18NextService.i18n.t("send_notifications_to_email")}
809 <div className="input-group mb-3">
810 <button type="submit" className="btn d-block btn-secondary me-4">
811 {this.state.saveRes.state === "loading" ? (
814 capitalizeFirstLetter(I18NextService.i18n.t("save"))
819 <div className="input-group mb-3">
821 className="btn d-block btn-danger"
824 this.handleDeleteAccountShowConfirmToggle
827 {I18NextService.i18n.t("delete_account")}
829 {this.state.deleteAccountShowConfirm && (
831 <div className="my-2 alert alert-danger" role="alert">
832 {I18NextService.i18n.t("delete_account_confirm")}
836 value={this.state.deleteAccountForm.password}
837 autoComplete="new-password"
841 this.handleDeleteAccountPasswordChange
843 className="form-control my-2"
846 className="btn btn-danger me-4"
847 disabled={!this.state.deleteAccountForm.password}
848 onClick={linkEvent(this, this.handleDeleteAccount)}
850 {this.state.deleteAccountRes.state === "loading" ? (
853 capitalizeFirstLetter(I18NextService.i18n.t("delete"))
857 className="btn btn-secondary"
860 this.handleDeleteAccountShowConfirmToggle
863 {I18NextService.i18n.t("cancel")}
875 UserService.Instance.myUserInfo?.local_user_view.local_user.totp_2fa_url;
880 <div className="input-group mb-3">
881 <div className="form-check">
883 className="form-check-input"
884 id="user-generate-totp"
886 checked={this.state.saveUserSettingsForm.generate_totp_2fa}
887 onChange={linkEvent(this, this.handleGenerateTotp)}
889 <label className="form-check-label" htmlFor="user-generate-totp">
890 {I18NextService.i18n.t("set_up_two_factor")}
899 <a className="btn btn-secondary mb-2" href={totpUrl}>
900 {I18NextService.i18n.t("two_factor_link")}
903 <div className="input-group mb-3">
904 <div className="form-check">
906 className="form-check-input"
907 id="user-remove-totp"
910 this.state.saveUserSettingsForm.generate_totp_2fa == false
912 onChange={linkEvent(this, this.handleRemoveTotp)}
914 <label className="form-check-label" htmlFor="user-remove-totp">
915 {I18NextService.i18n.t("remove_two_factor")}
925 handlePersonSearch = debounce(async (text: string) => {
926 this.setState({ searchPersonLoading: true });
928 const searchPersonOptions: Choice[] = [];
930 if (text.length > 0) {
931 searchPersonOptions.push(...(await fetchUsers(text)).map(personToChoice));
935 searchPersonLoading: false,
940 handleCommunitySearch = debounce(async (text: string) => {
941 this.setState({ searchCommunityLoading: true });
943 const searchCommunityOptions: Choice[] = [];
945 if (text.length > 0) {
946 searchCommunityOptions.push(
947 ...(await fetchCommunities(text)).map(communityToChoice)
952 searchCommunityLoading: false,
953 searchCommunityOptions,
957 async handleBlockPerson({ value }: Choice) {
959 const res = await HttpService.client.blockPerson({
960 person_id: Number(value),
962 auth: myAuthRequired(),
964 this.personBlock(res);
968 async handleUnblockPerson({
975 const res = await HttpService.client.blockPerson({
976 person_id: recipientId,
978 auth: myAuthRequired(),
980 ctx.personBlock(res);
983 async handleBlockCommunity({ value }: Choice) {
985 const res = await HttpService.client.blockCommunity({
986 community_id: Number(value),
988 auth: myAuthRequired(),
990 this.communityBlock(res);
994 async handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
995 const auth = myAuth();
997 const res = await HttpService.client.blockCommunity({
998 community_id: i.communityId,
1000 auth: myAuthRequired(),
1002 i.ctx.communityBlock(res);
1006 handleShowNsfwChange(i: Settings, event: any) {
1008 s => ((s.saveUserSettingsForm.show_nsfw = event.target.checked), s)
1012 handleShowAvatarsChange(i: Settings, event: any) {
1013 const mui = UserService.Instance.myUserInfo;
1015 mui.local_user_view.local_user.show_avatars = event.target.checked;
1018 s => ((s.saveUserSettingsForm.show_avatars = event.target.checked), s)
1022 handleBotAccount(i: Settings, event: any) {
1024 s => ((s.saveUserSettingsForm.bot_account = event.target.checked), s)
1028 handleShowBotAccounts(i: Settings, event: any) {
1031 (s.saveUserSettingsForm.show_bot_accounts = event.target.checked), s
1036 handleReadPosts(i: Settings, event: any) {
1038 s => ((s.saveUserSettingsForm.show_read_posts = event.target.checked), s)
1042 handleShowNewPostNotifs(i: Settings, event: any) {
1045 (s.saveUserSettingsForm.show_new_post_notifs = event.target.checked), s
1050 handleShowScoresChange(i: Settings, event: any) {
1051 const mui = UserService.Instance.myUserInfo;
1053 mui.local_user_view.local_user.show_scores = event.target.checked;
1056 s => ((s.saveUserSettingsForm.show_scores = event.target.checked), s)
1060 handleGenerateTotp(i: Settings, event: any) {
1061 // Coerce false to undefined here, so it won't generate it.
1062 const checked: boolean | undefined = event.target.checked || undefined;
1064 toast(I18NextService.i18n.t("two_factor_setup_instructions"));
1066 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1069 handleRemoveTotp(i: Settings, event: any) {
1070 // Coerce true to undefined here, so it won't generate it.
1071 const checked: boolean | undefined = !event.target.checked && undefined;
1072 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1075 handleSendNotificationsToEmailChange(i: Settings, event: any) {
1078 (s.saveUserSettingsForm.send_notifications_to_email =
1079 event.target.checked),
1085 handleThemeChange(i: Settings, event: any) {
1086 i.setState(s => ((s.saveUserSettingsForm.theme = event.target.value), s));
1087 setTheme(event.target.value, true);
1090 handleInterfaceLangChange(i: Settings, event: any) {
1091 const newLang = event.target.value ?? "browser";
1092 I18NextService.i18n.changeLanguage(
1093 newLang === "browser" ? navigator.languages : newLang
1097 s => ((s.saveUserSettingsForm.interface_language = event.target.value), s)
1101 handleDiscussionLanguageChange(val: number[]) {
1103 s => ((s.saveUserSettingsForm.discussion_languages = val), s)
1107 handleSortTypeChange(val: SortType) {
1108 this.setState(s => ((s.saveUserSettingsForm.default_sort_type = val), s));
1111 handleListingTypeChange(val: ListingType) {
1113 s => ((s.saveUserSettingsForm.default_listing_type = val), s)
1117 handleEmailChange(i: Settings, event: any) {
1118 i.setState(s => ((s.saveUserSettingsForm.email = event.target.value), s));
1121 handleBioChange(val: string) {
1122 this.setState(s => ((s.saveUserSettingsForm.bio = val), s));
1125 handleAvatarUpload(url: string) {
1126 this.setState(s => ((s.saveUserSettingsForm.avatar = url), s));
1129 handleAvatarRemove() {
1130 this.setState(s => ((s.saveUserSettingsForm.avatar = ""), s));
1133 handleBannerUpload(url: string) {
1134 this.setState(s => ((s.saveUserSettingsForm.banner = url), s));
1137 handleBannerRemove() {
1138 this.setState(s => ((s.saveUserSettingsForm.banner = ""), s));
1141 handleDisplayNameChange(i: Settings, event: any) {
1143 s => ((s.saveUserSettingsForm.display_name = event.target.value), s)
1147 handleMatrixUserIdChange(i: Settings, event: any) {
1149 s => ((s.saveUserSettingsForm.matrix_user_id = event.target.value), s)
1153 handleNewPasswordChange(i: Settings, event: any) {
1154 const newPass: string | undefined =
1155 event.target.value == "" ? undefined : event.target.value;
1156 i.setState(s => ((s.changePasswordForm.new_password = newPass), s));
1159 handleNewPasswordVerifyChange(i: Settings, event: any) {
1160 const newPassVerify: string | undefined =
1161 event.target.value == "" ? undefined : event.target.value;
1163 s => ((s.changePasswordForm.new_password_verify = newPassVerify), s)
1167 handleOldPasswordChange(i: Settings, event: any) {
1168 const oldPass: string | undefined =
1169 event.target.value == "" ? undefined : event.target.value;
1170 i.setState(s => ((s.changePasswordForm.old_password = oldPass), s));
1173 async handleSaveSettingsSubmit(i: Settings, event: any) {
1174 event.preventDefault();
1175 i.setState({ saveRes: { state: "loading" } });
1177 const saveRes = await HttpService.client.saveUserSettings({
1178 ...i.state.saveUserSettingsForm,
1179 auth: myAuthRequired(),
1182 if (saveRes.state === "success") {
1183 UserService.Instance.login({
1187 toast(I18NextService.i18n.t("saved"));
1188 window.scrollTo(0, 0);
1191 i.setState({ saveRes });
1194 async handleChangePasswordSubmit(i: Settings, event: any) {
1195 event.preventDefault();
1196 const { new_password, new_password_verify, old_password } =
1197 i.state.changePasswordForm;
1199 if (new_password && old_password && new_password_verify) {
1200 i.setState({ changePasswordRes: { state: "loading" } });
1201 const changePasswordRes = await HttpService.client.changePassword({
1203 new_password_verify,
1205 auth: myAuthRequired(),
1207 if (changePasswordRes.state === "success") {
1208 UserService.Instance.login({
1209 res: changePasswordRes.data,
1212 window.scrollTo(0, 0);
1213 toast(I18NextService.i18n.t("password_changed"));
1216 i.setState({ changePasswordRes });
1220 handleDeleteAccountShowConfirmToggle(i: Settings) {
1221 i.setState({ deleteAccountShowConfirm: !i.state.deleteAccountShowConfirm });
1224 handleDeleteAccountPasswordChange(i: Settings, event: any) {
1225 i.setState(s => ((s.deleteAccountForm.password = event.target.value), s));
1228 async handleDeleteAccount(i: Settings) {
1229 const password = i.state.deleteAccountForm.password;
1231 i.setState({ deleteAccountRes: { state: "loading" } });
1232 const deleteAccountRes = await HttpService.client.deleteAccount({
1234 auth: myAuthRequired(),
1236 if (deleteAccountRes.state === "success") {
1237 UserService.Instance.logout();
1238 this.context.router.history.replace("/");
1241 i.setState({ deleteAccountRes });
1245 handleSwitchTab(i: { ctx: Settings; tab: string }) {
1246 i.ctx.setState({ currentTab: i.tab });
1249 personBlock(res: RequestState<BlockPersonResponse>) {
1250 if (res.state === "success") {
1251 updatePersonBlock(res.data);
1252 const mui = UserService.Instance.myUserInfo;
1254 this.setState({ personBlocks: mui.person_blocks });
1259 communityBlock(res: RequestState<BlockCommunityResponse>) {
1260 if (res.state === "success") {
1261 updateCommunityBlock(res.data);
1262 const mui = UserService.Instance.myUserInfo;
1264 this.setState({ communityBlocks: mui.community_blocks });