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 PasswordInput from "../common/password-input";
44 import { SearchableSelect } from "../common/searchable-select";
45 import { SortSelect } from "../common/sort-select";
46 import Tabs from "../common/tabs";
47 import { CommunityLink } from "../community/community-link";
48 import { PersonListing } from "./person-listing";
50 interface SettingsState {
51 saveRes: RequestState<LoginResponse>;
52 changePasswordRes: RequestState<LoginResponse>;
53 deleteAccountRes: RequestState<DeleteAccountResponse>;
54 // TODO redo these forms
55 saveUserSettingsForm: {
58 default_sort_type?: SortType;
59 default_listing_type?: ListingType;
60 interface_language?: string;
63 display_name?: string;
66 matrix_user_id?: string;
67 show_avatars?: boolean;
68 show_scores?: boolean;
69 send_notifications_to_email?: boolean;
70 bot_account?: boolean;
71 show_bot_accounts?: boolean;
72 show_read_posts?: boolean;
73 show_new_post_notifs?: boolean;
74 discussion_languages?: number[];
75 generate_totp_2fa?: boolean;
78 new_password?: string;
79 new_password_verify?: string;
80 old_password?: string;
85 personBlocks: PersonBlockView[];
86 communityBlocks: CommunityBlockView[];
89 deleteAccountShowConfirm: boolean;
90 siteRes: GetSiteResponse;
91 searchCommunityLoading: boolean;
92 searchCommunityOptions: Choice[];
93 searchPersonLoading: boolean;
94 searchPersonOptions: Choice[];
97 type FilterType = "user" | "community";
106 filterType: FilterType;
108 onSearch: (text: string) => void;
109 onChange: (choice: Choice) => void;
112 <div className="mb-3 row">
114 className="col-md-4 col-form-label"
115 htmlFor={`block-${filterType}-filter`}
117 {I18NextService.i18n.t(`block_${filterType}` as NoOptionI18nKeys)}
119 <div className="col-md-8">
121 id={`block-${filterType}-filter`}
123 { label: emDash, value: "0", disabled: true } as Choice,
133 export class Settings extends Component<any, SettingsState> {
134 private isoData = setIsoData(this.context);
135 state: SettingsState = {
136 saveRes: { state: "empty" },
137 deleteAccountRes: { state: "empty" },
138 changePasswordRes: { state: "empty" },
139 saveUserSettingsForm: {},
140 changePasswordForm: {},
141 deleteAccountShowConfirm: false,
142 deleteAccountForm: {},
145 currentTab: "settings",
146 siteRes: this.isoData.site_res,
148 searchCommunityLoading: false,
149 searchCommunityOptions: [],
150 searchPersonLoading: false,
151 searchPersonOptions: [],
154 constructor(props: any, context: any) {
155 super(props, context);
157 this.handleSortTypeChange = this.handleSortTypeChange.bind(this);
158 this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
159 this.handleBioChange = this.handleBioChange.bind(this);
160 this.handleDiscussionLanguageChange =
161 this.handleDiscussionLanguageChange.bind(this);
163 this.handleAvatarUpload = this.handleAvatarUpload.bind(this);
164 this.handleAvatarRemove = this.handleAvatarRemove.bind(this);
166 this.handleBannerUpload = this.handleBannerUpload.bind(this);
167 this.handleBannerRemove = this.handleBannerRemove.bind(this);
168 this.userSettings = this.userSettings.bind(this);
169 this.blockCards = this.blockCards.bind(this);
171 this.handleBlockPerson = this.handleBlockPerson.bind(this);
172 this.handleBlockCommunity = this.handleBlockCommunity.bind(this);
174 const mui = UserService.Instance.myUserInfo;
181 default_listing_type,
187 show_new_post_notifs,
188 send_notifications_to_email,
199 } = mui.local_user_view;
203 personBlocks: mui.person_blocks,
204 communityBlocks: mui.community_blocks,
205 saveUserSettingsForm: {
206 ...this.state.saveUserSettingsForm,
208 theme: theme ?? "browser",
210 default_listing_type,
212 discussion_languages: mui.discussion_languages,
221 show_new_post_notifs,
224 send_notifications_to_email,
231 async componentDidMount() {
233 this.setState({ themeList: await fetchThemeList() });
236 get documentTitle(): string {
237 return I18NextService.i18n.t("settings");
242 <div className="person-settings container-lg">
244 title={this.documentTitle}
245 path={this.context.router.route.match.url}
246 description={this.documentTitle}
247 image={this.state.saveUserSettingsForm.avatar}
253 label: I18NextService.i18n.t("settings"),
254 getNode: this.userSettings,
258 label: I18NextService.i18n.t("blocks"),
259 getNode: this.blockCards,
267 userSettings(isSelected: boolean) {
270 className={classNames("tab-pane show", {
274 id="settings-tab-pane"
276 <div className="row">
277 <div className="col-12 col-md-6">
278 <div className="card border-secondary mb-3">
279 <div className="card-body">{this.saveUserSettingsHtmlForm()}</div>
282 <div className="col-12 col-md-6">
283 <div className="card border-secondary mb-3">
284 <div className="card-body">{this.changePasswordHtmlForm()}</div>
292 blockCards(isSelected: boolean) {
295 className={classNames("tab-pane", {
301 <div className="row">
302 <div className="col-12 col-md-6">
303 <div className="card border-secondary mb-3">
304 <div className="card-body">{this.blockUserCard()}</div>
307 <div className="col-12 col-md-6">
308 <div className="card border-secondary mb-3">
309 <div className="card-body">{this.blockCommunityCard()}</div>
317 changePasswordHtmlForm() {
320 <h2 className="h5">{I18NextService.i18n.t("change_password")}</h2>
321 <form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
322 <div className="mb-3">
325 value={this.state.changePasswordForm.new_password}
326 onInput={linkEvent(this, this.handleNewPasswordChange)}
328 label={I18NextService.i18n.t("new_password")}
332 <div className="mb-3">
334 id="verify-new-password"
335 value={this.state.changePasswordForm.new_password_verify}
336 onInput={linkEvent(this, this.handleNewPasswordVerifyChange)}
337 label={I18NextService.i18n.t("verify_password")}
341 <div className="mb-3">
343 id="user-old-password"
344 value={this.state.changePasswordForm.old_password}
345 onInput={linkEvent(this, this.handleOldPasswordChange)}
346 label={I18NextService.i18n.t("old_password")}
349 <div className="input-group mb-3">
352 className="btn d-block btn-secondary me-4 w-100"
354 {this.state.changePasswordRes.state === "loading" ? (
357 capitalizeFirstLetter(I18NextService.i18n.t("save"))
367 const { searchPersonLoading, searchPersonOptions } = this.state;
373 loading={searchPersonLoading}
374 onChange={this.handleBlockPerson}
375 onSearch={this.handlePersonSearch}
376 options={searchPersonOptions}
378 {this.blockedUsersList()}
386 <h2 className="h5">{I18NextService.i18n.t("blocked_users")}</h2>
387 <ul className="list-unstyled mb-0">
388 {this.state.personBlocks.map(pb => (
389 <li key={pb.target.id}>
391 <PersonListing person={pb.target} />
393 className="btn btn-sm"
395 { ctx: this, recipientId: pb.target.id },
396 this.handleUnblockPerson,
398 data-tippy-content={I18NextService.i18n.t("unblock_user")}
400 <Icon icon="x" classes="icon-inline" />
410 blockCommunityCard() {
411 const { searchCommunityLoading, searchCommunityOptions } = this.state;
416 filterType="community"
417 loading={searchCommunityLoading}
418 onChange={this.handleBlockCommunity}
419 onSearch={this.handleCommunitySearch}
420 options={searchCommunityOptions}
422 {this.blockedCommunitiesList()}
427 blockedCommunitiesList() {
430 <h2 className="h5">{I18NextService.i18n.t("blocked_communities")}</h2>
431 <ul className="list-unstyled mb-0">
432 {this.state.communityBlocks.map(cb => (
433 <li key={cb.community.id}>
435 <CommunityLink community={cb.community} />
437 className="btn btn-sm"
439 { ctx: this, communityId: cb.community.id },
440 this.handleUnblockCommunity,
442 data-tippy-content={I18NextService.i18n.t(
446 <Icon icon="x" classes="icon-inline" />
456 saveUserSettingsHtmlForm() {
457 const selectedLangs = this.state.saveUserSettingsForm.discussion_languages;
461 <h2 className="h5">{I18NextService.i18n.t("settings")}</h2>
462 <form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}>
463 <div className="mb-3 row">
464 <label className="col-sm-3 col-form-label" htmlFor="display-name">
465 {I18NextService.i18n.t("display_name")}
467 <div className="col-sm-9">
471 className="form-control"
472 placeholder={I18NextService.i18n.t("optional")}
473 value={this.state.saveUserSettingsForm.display_name}
474 onInput={linkEvent(this, this.handleDisplayNameChange)}
475 pattern="^(?!@)(.+)$"
480 <div className="mb-3 row">
481 <label className="col-sm-3 col-form-label" htmlFor="user-bio">
482 {I18NextService.i18n.t("bio")}
484 <div className="col-sm-9">
486 initialContent={this.state.saveUserSettingsForm.bio}
487 onContentChange={this.handleBioChange}
489 hideNavigationWarnings
490 allLanguages={this.state.siteRes.all_languages}
491 siteLanguages={this.state.siteRes.discussion_languages}
495 <div className="mb-3 row">
496 <label className="col-sm-3 col-form-label" htmlFor="user-email">
497 {I18NextService.i18n.t("email")}
499 <div className="col-sm-9">
503 className="form-control"
504 placeholder={I18NextService.i18n.t("optional")}
505 value={this.state.saveUserSettingsForm.email}
506 onInput={linkEvent(this, this.handleEmailChange)}
511 <div className="mb-3 row">
512 <label className="col-sm-3 col-form-label" htmlFor="matrix-user-id">
513 <a href={elementUrl} rel={relTags}>
514 {I18NextService.i18n.t("matrix_user_id")}
517 <div className="col-sm-9">
521 className="form-control"
522 placeholder="@user:example.com"
523 value={this.state.saveUserSettingsForm.matrix_user_id}
524 onInput={linkEvent(this, this.handleMatrixUserIdChange)}
525 pattern="^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
529 <div className="mb-3 row">
530 <label className="col-sm-3 col-form-label">
531 {I18NextService.i18n.t("avatar")}
533 <div className="col-sm-9">
535 uploadTitle={I18NextService.i18n.t("upload_avatar")}
536 imageSrc={this.state.saveUserSettingsForm.avatar}
537 onUpload={this.handleAvatarUpload}
538 onRemove={this.handleAvatarRemove}
543 <div className="mb-3 row">
544 <label className="col-sm-3 col-form-label">
545 {I18NextService.i18n.t("banner")}
547 <div className="col-sm-9">
549 uploadTitle={I18NextService.i18n.t("upload_banner")}
550 imageSrc={this.state.saveUserSettingsForm.banner}
551 onUpload={this.handleBannerUpload}
552 onRemove={this.handleBannerRemove}
556 <div className="mb-3 row">
557 <label className="col-sm-3 form-label" htmlFor="user-language">
558 {I18NextService.i18n.t("interface_language")}
560 <div className="col-sm-9">
563 value={this.state.saveUserSettingsForm.interface_language}
564 onChange={linkEvent(this, this.handleInterfaceLangChange)}
565 className="form-select d-inline-block w-auto"
567 <option disabled aria-hidden="true">
568 {I18NextService.i18n.t("interface_language")}
570 <option value="browser">
571 {I18NextService.i18n.t("browser_default")}
573 <option disabled aria-hidden="true">
577 .sort((a, b) => a.code.localeCompare(b.code))
579 <option key={lang.code} value={lang.code}>
587 allLanguages={this.state.siteRes.all_languages}
588 siteLanguages={this.state.siteRes.discussion_languages}
589 selectedLanguageIds={selectedLangs}
591 showLanguageWarning={true}
594 onChange={this.handleDiscussionLanguageChange}
596 <div className="mb-3 row">
597 <label className="col-sm-3 col-form-label" htmlFor="user-theme">
598 {I18NextService.i18n.t("theme")}
600 <div className="col-sm-9">
603 value={this.state.saveUserSettingsForm.theme}
604 onChange={linkEvent(this, this.handleThemeChange)}
605 className="form-select d-inline-block w-auto"
607 <option disabled aria-hidden="true">
608 {I18NextService.i18n.t("theme")}
610 <option value="browser">
611 {I18NextService.i18n.t("browser_default")}
613 <option value="browser-compact">
614 {I18NextService.i18n.t("browser_default_compact")}
616 {this.state.themeList.map(theme => (
617 <option key={theme} value={theme}>
624 <form className="mb-3 row">
625 <label className="col-sm-3 col-form-label">
626 {I18NextService.i18n.t("type")}
628 <div className="col-sm-9">
631 this.state.saveUserSettingsForm.default_listing_type ??
634 showLocal={showLocal(this.isoData)}
636 onChange={this.handleListingTypeChange}
640 <form className="mb-3 row">
641 <label className="col-sm-3 col-form-label">
642 {I18NextService.i18n.t("sort_type")}
644 <div className="col-sm-9">
647 this.state.saveUserSettingsForm.default_sort_type ?? "Active"
649 onChange={this.handleSortTypeChange}
653 <div className="input-group mb-3">
654 <div className="form-check">
656 className="form-check-input"
659 checked={this.state.saveUserSettingsForm.show_nsfw}
660 onChange={linkEvent(this, this.handleShowNsfwChange)}
662 <label className="form-check-label" htmlFor="user-show-nsfw">
663 {I18NextService.i18n.t("show_nsfw")}
667 <div className="input-group mb-3">
668 <div className="form-check">
670 className="form-check-input"
671 id="user-show-scores"
673 checked={this.state.saveUserSettingsForm.show_scores}
674 onChange={linkEvent(this, this.handleShowScoresChange)}
676 <label className="form-check-label" htmlFor="user-show-scores">
677 {I18NextService.i18n.t("show_scores")}
681 <div className="input-group mb-3">
682 <div className="form-check">
684 className="form-check-input"
685 id="user-show-avatars"
687 checked={this.state.saveUserSettingsForm.show_avatars}
688 onChange={linkEvent(this, this.handleShowAvatarsChange)}
690 <label className="form-check-label" htmlFor="user-show-avatars">
691 {I18NextService.i18n.t("show_avatars")}
695 <div className="input-group mb-3">
696 <div className="form-check">
698 className="form-check-input"
699 id="user-bot-account"
701 checked={this.state.saveUserSettingsForm.bot_account}
702 onChange={linkEvent(this, this.handleBotAccount)}
704 <label className="form-check-label" htmlFor="user-bot-account">
705 {I18NextService.i18n.t("bot_account")}
709 <div className="input-group mb-3">
710 <div className="form-check">
712 className="form-check-input"
713 id="user-show-bot-accounts"
715 checked={this.state.saveUserSettingsForm.show_bot_accounts}
716 onChange={linkEvent(this, this.handleShowBotAccounts)}
719 className="form-check-label"
720 htmlFor="user-show-bot-accounts"
722 {I18NextService.i18n.t("show_bot_accounts")}
726 <div className="input-group mb-3">
727 <div className="form-check">
729 className="form-check-input"
730 id="user-show-read-posts"
732 checked={this.state.saveUserSettingsForm.show_read_posts}
733 onChange={linkEvent(this, this.handleReadPosts)}
736 className="form-check-label"
737 htmlFor="user-show-read-posts"
739 {I18NextService.i18n.t("show_read_posts")}
743 <div className="input-group mb-3">
744 <div className="form-check">
746 className="form-check-input"
747 id="user-show-new-post-notifs"
749 checked={this.state.saveUserSettingsForm.show_new_post_notifs}
750 onChange={linkEvent(this, this.handleShowNewPostNotifs)}
753 className="form-check-label"
754 htmlFor="user-show-new-post-notifs"
756 {I18NextService.i18n.t("show_new_post_notifs")}
760 <div className="input-group mb-3">
761 <div className="form-check">
763 className="form-check-input"
764 id="user-send-notifications-to-email"
766 disabled={!this.state.saveUserSettingsForm.email}
768 this.state.saveUserSettingsForm.send_notifications_to_email
772 this.handleSendNotificationsToEmailChange,
776 className="form-check-label"
777 htmlFor="user-send-notifications-to-email"
779 {I18NextService.i18n.t("send_notifications_to_email")}
784 <div className="input-group mb-3">
785 <button type="submit" className="btn d-block btn-secondary me-4">
786 {this.state.saveRes.state === "loading" ? (
789 capitalizeFirstLetter(I18NextService.i18n.t("save"))
796 onSubmit={linkEvent(this, this.handleDeleteAccount)}
800 className="btn d-block btn-danger"
803 this.handleDeleteAccountShowConfirmToggle,
806 {I18NextService.i18n.t("delete_account")}
808 {this.state.deleteAccountShowConfirm && (
811 className="my-2 alert alert-danger d-block"
813 htmlFor="password-delete-account"
815 {I18NextService.i18n.t("delete_account_confirm")}
818 id="password-delete-account"
819 value={this.state.deleteAccountForm.password}
822 this.handleDeleteAccountPasswordChange,
828 className="btn btn-danger me-4"
829 disabled={!this.state.deleteAccountForm.password}
831 {this.state.deleteAccountRes.state === "loading" ? (
834 capitalizeFirstLetter(I18NextService.i18n.t("delete"))
838 className="btn btn-secondary"
842 this.handleDeleteAccountShowConfirmToggle,
845 {I18NextService.i18n.t("cancel")}
857 UserService.Instance.myUserInfo?.local_user_view.local_user.totp_2fa_url;
862 <div className="input-group mb-3">
863 <div className="form-check">
865 className="form-check-input"
866 id="user-generate-totp"
868 checked={this.state.saveUserSettingsForm.generate_totp_2fa}
869 onChange={linkEvent(this, this.handleGenerateTotp)}
871 <label className="form-check-label" htmlFor="user-generate-totp">
872 {I18NextService.i18n.t("set_up_two_factor")}
881 <a className="btn btn-secondary mb-2" href={totpUrl}>
882 {I18NextService.i18n.t("two_factor_link")}
885 <div className="input-group mb-3">
886 <div className="form-check">
888 className="form-check-input"
889 id="user-remove-totp"
892 this.state.saveUserSettingsForm.generate_totp_2fa === false
894 onChange={linkEvent(this, this.handleRemoveTotp)}
896 <label className="form-check-label" htmlFor="user-remove-totp">
897 {I18NextService.i18n.t("remove_two_factor")}
907 handlePersonSearch = debounce(async (text: string) => {
908 this.setState({ searchPersonLoading: true });
910 const searchPersonOptions: Choice[] = [];
912 if (text.length > 0) {
913 searchPersonOptions.push(...(await fetchUsers(text)).map(personToChoice));
917 searchPersonLoading: false,
922 handleCommunitySearch = debounce(async (text: string) => {
923 this.setState({ searchCommunityLoading: true });
925 const searchCommunityOptions: Choice[] = [];
927 if (text.length > 0) {
928 searchCommunityOptions.push(
929 ...(await fetchCommunities(text)).map(communityToChoice),
934 searchCommunityLoading: false,
935 searchCommunityOptions,
939 async handleBlockPerson({ value }: Choice) {
941 const res = await HttpService.client.blockPerson({
942 person_id: Number(value),
944 auth: myAuthRequired(),
946 this.personBlock(res);
950 async handleUnblockPerson({
957 const res = await HttpService.client.blockPerson({
958 person_id: recipientId,
960 auth: myAuthRequired(),
962 ctx.personBlock(res);
965 async handleBlockCommunity({ value }: Choice) {
967 const res = await HttpService.client.blockCommunity({
968 community_id: Number(value),
970 auth: myAuthRequired(),
972 this.communityBlock(res);
976 async handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
977 const auth = myAuth();
979 const res = await HttpService.client.blockCommunity({
980 community_id: i.communityId,
982 auth: myAuthRequired(),
984 i.ctx.communityBlock(res);
988 handleShowNsfwChange(i: Settings, event: any) {
990 s => ((s.saveUserSettingsForm.show_nsfw = event.target.checked), s),
994 handleShowAvatarsChange(i: Settings, event: any) {
995 const mui = UserService.Instance.myUserInfo;
997 mui.local_user_view.local_user.show_avatars = event.target.checked;
1000 s => ((s.saveUserSettingsForm.show_avatars = event.target.checked), s),
1004 handleBotAccount(i: Settings, event: any) {
1006 s => ((s.saveUserSettingsForm.bot_account = event.target.checked), s),
1010 handleShowBotAccounts(i: Settings, event: any) {
1013 (s.saveUserSettingsForm.show_bot_accounts = event.target.checked), s
1018 handleReadPosts(i: Settings, event: any) {
1020 s => ((s.saveUserSettingsForm.show_read_posts = event.target.checked), s),
1024 handleShowNewPostNotifs(i: Settings, event: any) {
1027 (s.saveUserSettingsForm.show_new_post_notifs = event.target.checked), s
1032 handleShowScoresChange(i: Settings, event: any) {
1033 const mui = UserService.Instance.myUserInfo;
1035 mui.local_user_view.local_user.show_scores = event.target.checked;
1038 s => ((s.saveUserSettingsForm.show_scores = event.target.checked), s),
1042 handleGenerateTotp(i: Settings, event: any) {
1043 // Coerce false to undefined here, so it won't generate it.
1044 const checked: boolean | undefined = event.target.checked || undefined;
1046 toast(I18NextService.i18n.t("two_factor_setup_instructions"));
1048 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1051 handleRemoveTotp(i: Settings, event: any) {
1052 // Coerce true to undefined here, so it won't generate it.
1053 const checked: boolean | undefined = !event.target.checked && undefined;
1054 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1057 handleSendNotificationsToEmailChange(i: Settings, event: any) {
1060 (s.saveUserSettingsForm.send_notifications_to_email =
1061 event.target.checked),
1067 handleThemeChange(i: Settings, event: any) {
1068 i.setState(s => ((s.saveUserSettingsForm.theme = event.target.value), s));
1069 setTheme(event.target.value, true);
1072 handleInterfaceLangChange(i: Settings, event: any) {
1073 const newLang = event.target.value ?? "browser";
1074 I18NextService.i18n.changeLanguage(
1075 newLang === "browser" ? navigator.languages : newLang,
1080 (s.saveUserSettingsForm.interface_language = event.target.value), s
1085 handleDiscussionLanguageChange(val: number[]) {
1087 s => ((s.saveUserSettingsForm.discussion_languages = val), s),
1091 handleSortTypeChange(val: SortType) {
1092 this.setState(s => ((s.saveUserSettingsForm.default_sort_type = val), s));
1095 handleListingTypeChange(val: ListingType) {
1097 s => ((s.saveUserSettingsForm.default_listing_type = val), s),
1101 handleEmailChange(i: Settings, event: any) {
1102 i.setState(s => ((s.saveUserSettingsForm.email = event.target.value), s));
1105 handleBioChange(val: string) {
1106 this.setState(s => ((s.saveUserSettingsForm.bio = val), s));
1109 handleAvatarUpload(url: string) {
1110 this.setState(s => ((s.saveUserSettingsForm.avatar = url), s));
1113 handleAvatarRemove() {
1114 this.setState(s => ((s.saveUserSettingsForm.avatar = ""), s));
1117 handleBannerUpload(url: string) {
1118 this.setState(s => ((s.saveUserSettingsForm.banner = url), s));
1121 handleBannerRemove() {
1122 this.setState(s => ((s.saveUserSettingsForm.banner = ""), s));
1125 handleDisplayNameChange(i: Settings, event: any) {
1127 s => ((s.saveUserSettingsForm.display_name = event.target.value), s),
1131 handleMatrixUserIdChange(i: Settings, event: any) {
1133 s => ((s.saveUserSettingsForm.matrix_user_id = event.target.value), s),
1137 handleNewPasswordChange(i: Settings, event: any) {
1138 const newPass: string | undefined =
1139 event.target.value === "" ? undefined : event.target.value;
1140 i.setState(s => ((s.changePasswordForm.new_password = newPass), s));
1143 handleNewPasswordVerifyChange(i: Settings, event: any) {
1144 const newPassVerify: string | undefined =
1145 event.target.value === "" ? undefined : event.target.value;
1147 s => ((s.changePasswordForm.new_password_verify = newPassVerify), s),
1151 handleOldPasswordChange(i: Settings, event: any) {
1152 const oldPass: string | undefined =
1153 event.target.value === "" ? undefined : event.target.value;
1154 i.setState(s => ((s.changePasswordForm.old_password = oldPass), s));
1157 async handleSaveSettingsSubmit(i: Settings, event: any) {
1158 event.preventDefault();
1159 i.setState({ saveRes: { state: "loading" } });
1161 const saveRes = await HttpService.client.saveUserSettings({
1162 ...i.state.saveUserSettingsForm,
1163 auth: myAuthRequired(),
1166 if (saveRes.state === "success") {
1167 UserService.Instance.login({
1171 toast(I18NextService.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({
1193 res: changePasswordRes.data,
1196 window.scrollTo(0, 0);
1197 toast(I18NextService.i18n.t("password_changed"));
1200 i.setState({ changePasswordRes });
1204 handleDeleteAccountShowConfirmToggle(i: Settings) {
1205 i.setState({ deleteAccountShowConfirm: !i.state.deleteAccountShowConfirm });
1208 handleDeleteAccountPasswordChange(i: Settings, event: any) {
1209 i.setState(s => ((s.deleteAccountForm.password = event.target.value), s));
1212 async handleDeleteAccount(i: Settings, event: Event) {
1213 event.preventDefault();
1214 const password = i.state.deleteAccountForm.password;
1216 i.setState({ deleteAccountRes: { state: "loading" } });
1217 const deleteAccountRes = await HttpService.client.deleteAccount({
1219 auth: myAuthRequired(),
1221 if (deleteAccountRes.state === "success") {
1222 UserService.Instance.logout();
1223 this.context.router.history.replace("/");
1226 i.setState({ deleteAccountRes });
1230 handleSwitchTab(i: { ctx: Settings; tab: string }) {
1231 i.ctx.setState({ currentTab: i.tab });
1234 personBlock(res: RequestState<BlockPersonResponse>) {
1235 if (res.state === "success") {
1236 updatePersonBlock(res.data);
1237 const mui = UserService.Instance.myUserInfo;
1239 this.setState({ personBlocks: mui.person_blocks });
1244 communityBlock(res: RequestState<BlockCommunityResponse>) {
1245 if (res.state === "success") {
1246 updateCommunityBlock(res.data);
1247 const mui = UserService.Instance.myUserInfo;
1249 this.setState({ communityBlocks: mui.community_blocks });