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 value="browser-compact">
600 {I18NextService.i18n.t("browser_default_compact")}
602 <option disabled aria-hidden="true">
606 .sort((a, b) => a.code.localeCompare(b.code))
608 <option key={lang.code} value={lang.code}>
616 allLanguages={this.state.siteRes.all_languages}
617 siteLanguages={this.state.siteRes.discussion_languages}
618 selectedLanguageIds={selectedLangs}
620 showLanguageWarning={true}
622 onChange={this.handleDiscussionLanguageChange}
624 <div className="mb-3 row">
625 <label className="col-sm-3 col-form-label" htmlFor="user-theme">
626 {I18NextService.i18n.t("theme")}
628 <div className="col-sm-9">
631 value={this.state.saveUserSettingsForm.theme}
632 onChange={linkEvent(this, this.handleThemeChange)}
633 className="form-select d-inline-block w-auto"
635 <option disabled aria-hidden="true">
636 {I18NextService.i18n.t("theme")}
638 <option value="browser">
639 {I18NextService.i18n.t("browser_default")}
641 <option value="browser-compact">
642 {I18NextService.i18n.t("browser_default_compact")}
644 {this.state.themeList.map(theme => (
645 <option key={theme} value={theme}>
652 <form className="mb-3 row">
653 <label className="col-sm-3 col-form-label">
654 {I18NextService.i18n.t("type")}
656 <div className="col-sm-9">
659 this.state.saveUserSettingsForm.default_listing_type ??
662 showLocal={showLocal(this.isoData)}
664 onChange={this.handleListingTypeChange}
668 <form className="mb-3 row">
669 <label className="col-sm-3 col-form-label">
670 {I18NextService.i18n.t("sort_type")}
672 <div className="col-sm-9">
675 this.state.saveUserSettingsForm.default_sort_type ?? "Active"
677 onChange={this.handleSortTypeChange}
681 <div className="input-group mb-3">
682 <div className="form-check">
684 className="form-check-input"
687 checked={this.state.saveUserSettingsForm.show_nsfw}
688 onChange={linkEvent(this, this.handleShowNsfwChange)}
690 <label className="form-check-label" htmlFor="user-show-nsfw">
691 {I18NextService.i18n.t("show_nsfw")}
695 <div className="input-group mb-3">
696 <div className="form-check">
698 className="form-check-input"
699 id="user-show-scores"
701 checked={this.state.saveUserSettingsForm.show_scores}
702 onChange={linkEvent(this, this.handleShowScoresChange)}
704 <label className="form-check-label" htmlFor="user-show-scores">
705 {I18NextService.i18n.t("show_scores")}
709 <div className="input-group mb-3">
710 <div className="form-check">
712 className="form-check-input"
713 id="user-show-avatars"
715 checked={this.state.saveUserSettingsForm.show_avatars}
716 onChange={linkEvent(this, this.handleShowAvatarsChange)}
718 <label className="form-check-label" htmlFor="user-show-avatars">
719 {I18NextService.i18n.t("show_avatars")}
723 <div className="input-group mb-3">
724 <div className="form-check">
726 className="form-check-input"
727 id="user-bot-account"
729 checked={this.state.saveUserSettingsForm.bot_account}
730 onChange={linkEvent(this, this.handleBotAccount)}
732 <label className="form-check-label" htmlFor="user-bot-account">
733 {I18NextService.i18n.t("bot_account")}
737 <div className="input-group mb-3">
738 <div className="form-check">
740 className="form-check-input"
741 id="user-show-bot-accounts"
743 checked={this.state.saveUserSettingsForm.show_bot_accounts}
744 onChange={linkEvent(this, this.handleShowBotAccounts)}
747 className="form-check-label"
748 htmlFor="user-show-bot-accounts"
750 {I18NextService.i18n.t("show_bot_accounts")}
754 <div className="input-group mb-3">
755 <div className="form-check">
757 className="form-check-input"
758 id="user-show-read-posts"
760 checked={this.state.saveUserSettingsForm.show_read_posts}
761 onChange={linkEvent(this, this.handleReadPosts)}
764 className="form-check-label"
765 htmlFor="user-show-read-posts"
767 {I18NextService.i18n.t("show_read_posts")}
771 <div className="input-group mb-3">
772 <div className="form-check">
774 className="form-check-input"
775 id="user-show-new-post-notifs"
777 checked={this.state.saveUserSettingsForm.show_new_post_notifs}
778 onChange={linkEvent(this, this.handleShowNewPostNotifs)}
781 className="form-check-label"
782 htmlFor="user-show-new-post-notifs"
784 {I18NextService.i18n.t("show_new_post_notifs")}
788 <div className="input-group mb-3">
789 <div className="form-check">
791 className="form-check-input"
792 id="user-send-notifications-to-email"
794 disabled={!this.state.saveUserSettingsForm.email}
796 this.state.saveUserSettingsForm.send_notifications_to_email
800 this.handleSendNotificationsToEmailChange
804 className="form-check-label"
805 htmlFor="user-send-notifications-to-email"
807 {I18NextService.i18n.t("send_notifications_to_email")}
812 <div className="input-group mb-3">
813 <button type="submit" className="btn d-block btn-secondary me-4">
814 {this.state.saveRes.state === "loading" ? (
817 capitalizeFirstLetter(I18NextService.i18n.t("save"))
822 <div className="input-group mb-3">
824 className="btn d-block btn-danger"
827 this.handleDeleteAccountShowConfirmToggle
830 {I18NextService.i18n.t("delete_account")}
832 {this.state.deleteAccountShowConfirm && (
834 <div className="my-2 alert alert-danger" role="alert">
835 {I18NextService.i18n.t("delete_account_confirm")}
839 value={this.state.deleteAccountForm.password}
840 autoComplete="new-password"
844 this.handleDeleteAccountPasswordChange
846 className="form-control my-2"
849 className="btn btn-danger me-4"
850 disabled={!this.state.deleteAccountForm.password}
851 onClick={linkEvent(this, this.handleDeleteAccount)}
853 {this.state.deleteAccountRes.state === "loading" ? (
856 capitalizeFirstLetter(I18NextService.i18n.t("delete"))
860 className="btn btn-secondary"
863 this.handleDeleteAccountShowConfirmToggle
866 {I18NextService.i18n.t("cancel")}
878 UserService.Instance.myUserInfo?.local_user_view.local_user.totp_2fa_url;
883 <div className="input-group mb-3">
884 <div className="form-check">
886 className="form-check-input"
887 id="user-generate-totp"
889 checked={this.state.saveUserSettingsForm.generate_totp_2fa}
890 onChange={linkEvent(this, this.handleGenerateTotp)}
892 <label className="form-check-label" htmlFor="user-generate-totp">
893 {I18NextService.i18n.t("set_up_two_factor")}
902 <a className="btn btn-secondary mb-2" href={totpUrl}>
903 {I18NextService.i18n.t("two_factor_link")}
906 <div className="input-group mb-3">
907 <div className="form-check">
909 className="form-check-input"
910 id="user-remove-totp"
913 this.state.saveUserSettingsForm.generate_totp_2fa == false
915 onChange={linkEvent(this, this.handleRemoveTotp)}
917 <label className="form-check-label" htmlFor="user-remove-totp">
918 {I18NextService.i18n.t("remove_two_factor")}
928 handlePersonSearch = debounce(async (text: string) => {
929 this.setState({ searchPersonLoading: true });
931 const searchPersonOptions: Choice[] = [];
933 if (text.length > 0) {
934 searchPersonOptions.push(...(await fetchUsers(text)).map(personToChoice));
938 searchPersonLoading: false,
943 handleCommunitySearch = debounce(async (text: string) => {
944 this.setState({ searchCommunityLoading: true });
946 const searchCommunityOptions: Choice[] = [];
948 if (text.length > 0) {
949 searchCommunityOptions.push(
950 ...(await fetchCommunities(text)).map(communityToChoice)
955 searchCommunityLoading: false,
956 searchCommunityOptions,
960 async handleBlockPerson({ value }: Choice) {
962 const res = await HttpService.client.blockPerson({
963 person_id: Number(value),
965 auth: myAuthRequired(),
967 this.personBlock(res);
971 async handleUnblockPerson({
978 const res = await HttpService.client.blockPerson({
979 person_id: recipientId,
981 auth: myAuthRequired(),
983 ctx.personBlock(res);
986 async handleBlockCommunity({ value }: Choice) {
988 const res = await HttpService.client.blockCommunity({
989 community_id: Number(value),
991 auth: myAuthRequired(),
993 this.communityBlock(res);
997 async handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
998 const auth = myAuth();
1000 const res = await HttpService.client.blockCommunity({
1001 community_id: i.communityId,
1003 auth: myAuthRequired(),
1005 i.ctx.communityBlock(res);
1009 handleShowNsfwChange(i: Settings, event: any) {
1011 s => ((s.saveUserSettingsForm.show_nsfw = event.target.checked), s)
1015 handleShowAvatarsChange(i: Settings, event: any) {
1016 const mui = UserService.Instance.myUserInfo;
1018 mui.local_user_view.local_user.show_avatars = event.target.checked;
1021 s => ((s.saveUserSettingsForm.show_avatars = event.target.checked), s)
1025 handleBotAccount(i: Settings, event: any) {
1027 s => ((s.saveUserSettingsForm.bot_account = event.target.checked), s)
1031 handleShowBotAccounts(i: Settings, event: any) {
1034 (s.saveUserSettingsForm.show_bot_accounts = event.target.checked), s
1039 handleReadPosts(i: Settings, event: any) {
1041 s => ((s.saveUserSettingsForm.show_read_posts = event.target.checked), s)
1045 handleShowNewPostNotifs(i: Settings, event: any) {
1048 (s.saveUserSettingsForm.show_new_post_notifs = event.target.checked), s
1053 handleShowScoresChange(i: Settings, event: any) {
1054 const mui = UserService.Instance.myUserInfo;
1056 mui.local_user_view.local_user.show_scores = event.target.checked;
1059 s => ((s.saveUserSettingsForm.show_scores = event.target.checked), s)
1063 handleGenerateTotp(i: Settings, event: any) {
1064 // Coerce false to undefined here, so it won't generate it.
1065 const checked: boolean | undefined = event.target.checked || undefined;
1067 toast(I18NextService.i18n.t("two_factor_setup_instructions"));
1069 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1072 handleRemoveTotp(i: Settings, event: any) {
1073 // Coerce true to undefined here, so it won't generate it.
1074 const checked: boolean | undefined = !event.target.checked && undefined;
1075 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1078 handleSendNotificationsToEmailChange(i: Settings, event: any) {
1081 (s.saveUserSettingsForm.send_notifications_to_email =
1082 event.target.checked),
1088 handleThemeChange(i: Settings, event: any) {
1089 i.setState(s => ((s.saveUserSettingsForm.theme = event.target.value), s));
1090 setTheme(event.target.value, true);
1093 handleInterfaceLangChange(i: Settings, event: any) {
1094 const newLang = event.target.value ?? "browser";
1095 I18NextService.i18n.changeLanguage(
1096 newLang === "browser" ? navigator.languages : newLang
1100 s => ((s.saveUserSettingsForm.interface_language = event.target.value), s)
1104 handleDiscussionLanguageChange(val: number[]) {
1106 s => ((s.saveUserSettingsForm.discussion_languages = val), s)
1110 handleSortTypeChange(val: SortType) {
1111 this.setState(s => ((s.saveUserSettingsForm.default_sort_type = val), s));
1114 handleListingTypeChange(val: ListingType) {
1116 s => ((s.saveUserSettingsForm.default_listing_type = val), s)
1120 handleEmailChange(i: Settings, event: any) {
1121 i.setState(s => ((s.saveUserSettingsForm.email = event.target.value), s));
1124 handleBioChange(val: string) {
1125 this.setState(s => ((s.saveUserSettingsForm.bio = val), s));
1128 handleAvatarUpload(url: string) {
1129 this.setState(s => ((s.saveUserSettingsForm.avatar = url), s));
1132 handleAvatarRemove() {
1133 this.setState(s => ((s.saveUserSettingsForm.avatar = ""), s));
1136 handleBannerUpload(url: string) {
1137 this.setState(s => ((s.saveUserSettingsForm.banner = url), s));
1140 handleBannerRemove() {
1141 this.setState(s => ((s.saveUserSettingsForm.banner = ""), s));
1144 handleDisplayNameChange(i: Settings, event: any) {
1146 s => ((s.saveUserSettingsForm.display_name = event.target.value), s)
1150 handleMatrixUserIdChange(i: Settings, event: any) {
1152 s => ((s.saveUserSettingsForm.matrix_user_id = event.target.value), s)
1156 handleNewPasswordChange(i: Settings, event: any) {
1157 const newPass: string | undefined =
1158 event.target.value == "" ? undefined : event.target.value;
1159 i.setState(s => ((s.changePasswordForm.new_password = newPass), s));
1162 handleNewPasswordVerifyChange(i: Settings, event: any) {
1163 const newPassVerify: string | undefined =
1164 event.target.value == "" ? undefined : event.target.value;
1166 s => ((s.changePasswordForm.new_password_verify = newPassVerify), s)
1170 handleOldPasswordChange(i: Settings, event: any) {
1171 const oldPass: string | undefined =
1172 event.target.value == "" ? undefined : event.target.value;
1173 i.setState(s => ((s.changePasswordForm.old_password = oldPass), s));
1176 async handleSaveSettingsSubmit(i: Settings, event: any) {
1177 event.preventDefault();
1178 i.setState({ saveRes: { state: "loading" } });
1180 const saveRes = await HttpService.client.saveUserSettings({
1181 ...i.state.saveUserSettingsForm,
1182 auth: myAuthRequired(),
1185 if (saveRes.state === "success") {
1186 UserService.Instance.login({
1190 toast(I18NextService.i18n.t("saved"));
1191 window.scrollTo(0, 0);
1194 i.setState({ saveRes });
1197 async handleChangePasswordSubmit(i: Settings, event: any) {
1198 event.preventDefault();
1199 const { new_password, new_password_verify, old_password } =
1200 i.state.changePasswordForm;
1202 if (new_password && old_password && new_password_verify) {
1203 i.setState({ changePasswordRes: { state: "loading" } });
1204 const changePasswordRes = await HttpService.client.changePassword({
1206 new_password_verify,
1208 auth: myAuthRequired(),
1210 if (changePasswordRes.state === "success") {
1211 UserService.Instance.login({
1212 res: changePasswordRes.data,
1215 window.scrollTo(0, 0);
1216 toast(I18NextService.i18n.t("password_changed"));
1219 i.setState({ changePasswordRes });
1223 handleDeleteAccountShowConfirmToggle(i: Settings) {
1224 i.setState({ deleteAccountShowConfirm: !i.state.deleteAccountShowConfirm });
1227 handleDeleteAccountPasswordChange(i: Settings, event: any) {
1228 i.setState(s => ((s.deleteAccountForm.password = event.target.value), s));
1231 async handleDeleteAccount(i: Settings) {
1232 const password = i.state.deleteAccountForm.password;
1234 i.setState({ deleteAccountRes: { state: "loading" } });
1235 const deleteAccountRes = await HttpService.client.deleteAccount({
1237 auth: myAuthRequired(),
1239 if (deleteAccountRes.state === "success") {
1240 UserService.Instance.logout();
1241 this.context.router.history.replace("/");
1244 i.setState({ deleteAccountRes });
1248 handleSwitchTab(i: { ctx: Settings; tab: string }) {
1249 i.ctx.setState({ currentTab: i.tab });
1252 personBlock(res: RequestState<BlockPersonResponse>) {
1253 if (res.state === "success") {
1254 updatePersonBlock(res.data);
1255 const mui = UserService.Instance.myUserInfo;
1257 this.setState({ personBlocks: mui.person_blocks });
1262 communityBlock(res: RequestState<BlockCommunityResponse>) {
1263 if (res.state === "success") {
1264 updateCommunityBlock(res.data);
1265 const mui = UserService.Instance.myUserInfo;
1267 this.setState({ communityBlocks: mui.community_blocks });