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 <h5>{I18NextService.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 {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 <h5>{I18NextService.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={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 <h5>{I18NextService.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={I18NextService.i18n.t(
472 <Icon icon="x" classes="icon-inline" />
482 saveUserSettingsHtmlForm() {
483 const selectedLangs = this.state.saveUserSettingsForm.discussion_languages;
487 <h5>{I18NextService.i18n.t("settings")}</h5>
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 {this.state.themeList.map(theme => (
639 <option key={theme} value={theme}>
646 <form className="mb-3 row">
647 <label className="col-sm-3 col-form-label">
648 {I18NextService.i18n.t("type")}
650 <div className="col-sm-9">
653 this.state.saveUserSettingsForm.default_listing_type ??
656 showLocal={showLocal(this.isoData)}
658 onChange={this.handleListingTypeChange}
662 <form className="mb-3 row">
663 <label className="col-sm-3 col-form-label">
664 {I18NextService.i18n.t("sort_type")}
666 <div className="col-sm-9">
669 this.state.saveUserSettingsForm.default_sort_type ?? "Active"
671 onChange={this.handleSortTypeChange}
675 <div className="input-group mb-3">
676 <div className="form-check">
678 className="form-check-input"
681 checked={this.state.saveUserSettingsForm.show_nsfw}
682 onChange={linkEvent(this, this.handleShowNsfwChange)}
684 <label className="form-check-label" htmlFor="user-show-nsfw">
685 {I18NextService.i18n.t("show_nsfw")}
689 <div className="input-group mb-3">
690 <div className="form-check">
692 className="form-check-input"
693 id="user-show-scores"
695 checked={this.state.saveUserSettingsForm.show_scores}
696 onChange={linkEvent(this, this.handleShowScoresChange)}
698 <label className="form-check-label" htmlFor="user-show-scores">
699 {I18NextService.i18n.t("show_scores")}
703 <div className="input-group mb-3">
704 <div className="form-check">
706 className="form-check-input"
707 id="user-show-avatars"
709 checked={this.state.saveUserSettingsForm.show_avatars}
710 onChange={linkEvent(this, this.handleShowAvatarsChange)}
712 <label className="form-check-label" htmlFor="user-show-avatars">
713 {I18NextService.i18n.t("show_avatars")}
717 <div className="input-group mb-3">
718 <div className="form-check">
720 className="form-check-input"
721 id="user-bot-account"
723 checked={this.state.saveUserSettingsForm.bot_account}
724 onChange={linkEvent(this, this.handleBotAccount)}
726 <label className="form-check-label" htmlFor="user-bot-account">
727 {I18NextService.i18n.t("bot_account")}
731 <div className="input-group mb-3">
732 <div className="form-check">
734 className="form-check-input"
735 id="user-show-bot-accounts"
737 checked={this.state.saveUserSettingsForm.show_bot_accounts}
738 onChange={linkEvent(this, this.handleShowBotAccounts)}
741 className="form-check-label"
742 htmlFor="user-show-bot-accounts"
744 {I18NextService.i18n.t("show_bot_accounts")}
748 <div className="input-group mb-3">
749 <div className="form-check">
751 className="form-check-input"
752 id="user-show-read-posts"
754 checked={this.state.saveUserSettingsForm.show_read_posts}
755 onChange={linkEvent(this, this.handleReadPosts)}
758 className="form-check-label"
759 htmlFor="user-show-read-posts"
761 {I18NextService.i18n.t("show_read_posts")}
765 <div className="input-group mb-3">
766 <div className="form-check">
768 className="form-check-input"
769 id="user-show-new-post-notifs"
771 checked={this.state.saveUserSettingsForm.show_new_post_notifs}
772 onChange={linkEvent(this, this.handleShowNewPostNotifs)}
775 className="form-check-label"
776 htmlFor="user-show-new-post-notifs"
778 {I18NextService.i18n.t("show_new_post_notifs")}
782 <div className="input-group mb-3">
783 <div className="form-check">
785 className="form-check-input"
786 id="user-send-notifications-to-email"
788 disabled={!this.state.saveUserSettingsForm.email}
790 this.state.saveUserSettingsForm.send_notifications_to_email
794 this.handleSendNotificationsToEmailChange
798 className="form-check-label"
799 htmlFor="user-send-notifications-to-email"
801 {I18NextService.i18n.t("send_notifications_to_email")}
806 <div className="input-group mb-3">
807 <button type="submit" className="btn d-block btn-secondary me-4">
808 {this.state.saveRes.state === "loading" ? (
811 capitalizeFirstLetter(I18NextService.i18n.t("save"))
816 <div className="input-group mb-3">
818 className="btn d-block btn-danger"
821 this.handleDeleteAccountShowConfirmToggle
824 {I18NextService.i18n.t("delete_account")}
826 {this.state.deleteAccountShowConfirm && (
828 <div className="my-2 alert alert-danger" role="alert">
829 {I18NextService.i18n.t("delete_account_confirm")}
833 value={this.state.deleteAccountForm.password}
834 autoComplete="new-password"
838 this.handleDeleteAccountPasswordChange
840 className="form-control my-2"
843 className="btn btn-danger me-4"
844 disabled={!this.state.deleteAccountForm.password}
845 onClick={linkEvent(this, this.handleDeleteAccount)}
847 {this.state.deleteAccountRes.state === "loading" ? (
850 capitalizeFirstLetter(I18NextService.i18n.t("delete"))
854 className="btn btn-secondary"
857 this.handleDeleteAccountShowConfirmToggle
860 {I18NextService.i18n.t("cancel")}
872 UserService.Instance.myUserInfo?.local_user_view.local_user.totp_2fa_url;
877 <div className="input-group mb-3">
878 <div className="form-check">
880 className="form-check-input"
881 id="user-generate-totp"
883 checked={this.state.saveUserSettingsForm.generate_totp_2fa}
884 onChange={linkEvent(this, this.handleGenerateTotp)}
886 <label className="form-check-label" htmlFor="user-generate-totp">
887 {I18NextService.i18n.t("set_up_two_factor")}
896 <a className="btn btn-secondary mb-2" href={totpUrl}>
897 {I18NextService.i18n.t("two_factor_link")}
900 <div className="input-group mb-3">
901 <div className="form-check">
903 className="form-check-input"
904 id="user-remove-totp"
907 this.state.saveUserSettingsForm.generate_totp_2fa == false
909 onChange={linkEvent(this, this.handleRemoveTotp)}
911 <label className="form-check-label" htmlFor="user-remove-totp">
912 {I18NextService.i18n.t("remove_two_factor")}
922 handlePersonSearch = debounce(async (text: string) => {
923 this.setState({ searchPersonLoading: true });
925 const searchPersonOptions: Choice[] = [];
927 if (text.length > 0) {
928 searchPersonOptions.push(...(await fetchUsers(text)).map(personToChoice));
932 searchPersonLoading: false,
937 handleCommunitySearch = debounce(async (text: string) => {
938 this.setState({ searchCommunityLoading: true });
940 const searchCommunityOptions: Choice[] = [];
942 if (text.length > 0) {
943 searchCommunityOptions.push(
944 ...(await fetchCommunities(text)).map(communityToChoice)
949 searchCommunityLoading: false,
950 searchCommunityOptions,
954 async handleBlockPerson({ value }: Choice) {
956 const res = await HttpService.client.blockPerson({
957 person_id: Number(value),
959 auth: myAuthRequired(),
961 this.personBlock(res);
965 async handleUnblockPerson({
972 const res = await HttpService.client.blockPerson({
973 person_id: recipientId,
975 auth: myAuthRequired(),
977 ctx.personBlock(res);
980 async handleBlockCommunity({ value }: Choice) {
982 const res = await HttpService.client.blockCommunity({
983 community_id: Number(value),
985 auth: myAuthRequired(),
987 this.communityBlock(res);
991 async handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
992 const auth = myAuth();
994 const res = await HttpService.client.blockCommunity({
995 community_id: i.communityId,
997 auth: myAuthRequired(),
999 i.ctx.communityBlock(res);
1003 handleShowNsfwChange(i: Settings, event: any) {
1005 s => ((s.saveUserSettingsForm.show_nsfw = event.target.checked), s)
1009 handleShowAvatarsChange(i: Settings, event: any) {
1010 const mui = UserService.Instance.myUserInfo;
1012 mui.local_user_view.local_user.show_avatars = event.target.checked;
1015 s => ((s.saveUserSettingsForm.show_avatars = event.target.checked), s)
1019 handleBotAccount(i: Settings, event: any) {
1021 s => ((s.saveUserSettingsForm.bot_account = event.target.checked), s)
1025 handleShowBotAccounts(i: Settings, event: any) {
1028 (s.saveUserSettingsForm.show_bot_accounts = event.target.checked), s
1033 handleReadPosts(i: Settings, event: any) {
1035 s => ((s.saveUserSettingsForm.show_read_posts = event.target.checked), s)
1039 handleShowNewPostNotifs(i: Settings, event: any) {
1042 (s.saveUserSettingsForm.show_new_post_notifs = event.target.checked), s
1047 handleShowScoresChange(i: Settings, event: any) {
1048 const mui = UserService.Instance.myUserInfo;
1050 mui.local_user_view.local_user.show_scores = event.target.checked;
1053 s => ((s.saveUserSettingsForm.show_scores = event.target.checked), s)
1057 handleGenerateTotp(i: Settings, event: any) {
1058 // Coerce false to undefined here, so it won't generate it.
1059 const checked: boolean | undefined = event.target.checked || undefined;
1061 toast(I18NextService.i18n.t("two_factor_setup_instructions"));
1063 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1066 handleRemoveTotp(i: Settings, event: any) {
1067 // Coerce true to undefined here, so it won't generate it.
1068 const checked: boolean | undefined = !event.target.checked && undefined;
1069 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1072 handleSendNotificationsToEmailChange(i: Settings, event: any) {
1075 (s.saveUserSettingsForm.send_notifications_to_email =
1076 event.target.checked),
1082 handleThemeChange(i: Settings, event: any) {
1083 i.setState(s => ((s.saveUserSettingsForm.theme = event.target.value), s));
1084 setTheme(event.target.value, true);
1087 handleInterfaceLangChange(i: Settings, event: any) {
1088 const newLang = event.target.value ?? "browser";
1089 I18NextService.i18n.changeLanguage(
1090 newLang === "browser" ? navigator.languages : newLang
1094 s => ((s.saveUserSettingsForm.interface_language = event.target.value), s)
1098 handleDiscussionLanguageChange(val: number[]) {
1100 s => ((s.saveUserSettingsForm.discussion_languages = val), s)
1104 handleSortTypeChange(val: SortType) {
1105 this.setState(s => ((s.saveUserSettingsForm.default_sort_type = val), s));
1108 handleListingTypeChange(val: ListingType) {
1110 s => ((s.saveUserSettingsForm.default_listing_type = val), s)
1114 handleEmailChange(i: Settings, event: any) {
1115 i.setState(s => ((s.saveUserSettingsForm.email = event.target.value), s));
1118 handleBioChange(val: string) {
1119 this.setState(s => ((s.saveUserSettingsForm.bio = val), s));
1122 handleAvatarUpload(url: string) {
1123 this.setState(s => ((s.saveUserSettingsForm.avatar = url), s));
1126 handleAvatarRemove() {
1127 this.setState(s => ((s.saveUserSettingsForm.avatar = ""), s));
1130 handleBannerUpload(url: string) {
1131 this.setState(s => ((s.saveUserSettingsForm.banner = url), s));
1134 handleBannerRemove() {
1135 this.setState(s => ((s.saveUserSettingsForm.banner = ""), s));
1138 handleDisplayNameChange(i: Settings, event: any) {
1140 s => ((s.saveUserSettingsForm.display_name = event.target.value), s)
1144 handleMatrixUserIdChange(i: Settings, event: any) {
1146 s => ((s.saveUserSettingsForm.matrix_user_id = event.target.value), s)
1150 handleNewPasswordChange(i: Settings, event: any) {
1151 const newPass: string | undefined =
1152 event.target.value == "" ? undefined : event.target.value;
1153 i.setState(s => ((s.changePasswordForm.new_password = newPass), s));
1156 handleNewPasswordVerifyChange(i: Settings, event: any) {
1157 const newPassVerify: string | undefined =
1158 event.target.value == "" ? undefined : event.target.value;
1160 s => ((s.changePasswordForm.new_password_verify = newPassVerify), s)
1164 handleOldPasswordChange(i: Settings, event: any) {
1165 const oldPass: string | undefined =
1166 event.target.value == "" ? undefined : event.target.value;
1167 i.setState(s => ((s.changePasswordForm.old_password = oldPass), s));
1170 async handleSaveSettingsSubmit(i: Settings, event: any) {
1171 event.preventDefault();
1172 i.setState({ saveRes: { state: "loading" } });
1174 const saveRes = await HttpService.client.saveUserSettings({
1175 ...i.state.saveUserSettingsForm,
1176 auth: myAuthRequired(),
1178 if (saveRes.state === "success") {
1179 UserService.Instance.login(saveRes.data);
1181 toast(I18NextService.i18n.t("saved"));
1182 window.scrollTo(0, 0);
1185 i.setState({ saveRes });
1188 async handleChangePasswordSubmit(i: Settings, event: any) {
1189 event.preventDefault();
1190 const { new_password, new_password_verify, old_password } =
1191 i.state.changePasswordForm;
1193 if (new_password && old_password && new_password_verify) {
1194 i.setState({ changePasswordRes: { state: "loading" } });
1195 const changePasswordRes = await HttpService.client.changePassword({
1197 new_password_verify,
1199 auth: myAuthRequired(),
1201 if (changePasswordRes.state === "success") {
1202 UserService.Instance.login(changePasswordRes.data);
1203 window.scrollTo(0, 0);
1204 toast(I18NextService.i18n.t("password_changed"));
1207 i.setState({ changePasswordRes });
1211 handleDeleteAccountShowConfirmToggle(i: Settings) {
1212 i.setState({ deleteAccountShowConfirm: !i.state.deleteAccountShowConfirm });
1215 handleDeleteAccountPasswordChange(i: Settings, event: any) {
1216 i.setState(s => ((s.deleteAccountForm.password = event.target.value), s));
1219 async handleDeleteAccount(i: Settings) {
1220 const password = i.state.deleteAccountForm.password;
1222 i.setState({ deleteAccountRes: { state: "loading" } });
1223 const deleteAccountRes = await HttpService.client.deleteAccount({
1225 auth: myAuthRequired(),
1227 if (deleteAccountRes.state === "success") {
1228 UserService.Instance.logout();
1229 this.context.router.history.replace("/");
1232 i.setState({ deleteAccountRes });
1236 handleSwitchTab(i: { ctx: Settings; tab: string }) {
1237 i.ctx.setState({ currentTab: i.tab });
1240 personBlock(res: RequestState<BlockPersonResponse>) {
1241 if (res.state === "success") {
1242 updatePersonBlock(res.data);
1243 const mui = UserService.Instance.myUserInfo;
1245 this.setState({ personBlocks: mui.person_blocks });
1250 communityBlock(res: RequestState<BlockCommunityResponse>) {
1251 if (res.state === "success") {
1252 updateCommunityBlock(res.data);
1253 const mui = UserService.Instance.myUserInfo;
1255 this.setState({ communityBlocks: mui.community_blocks });