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;
76 open_links_in_new_tab?: boolean;
79 new_password?: string;
80 new_password_verify?: string;
81 old_password?: string;
86 personBlocks: PersonBlockView[];
87 communityBlocks: CommunityBlockView[];
90 deleteAccountShowConfirm: boolean;
91 siteRes: GetSiteResponse;
92 searchCommunityLoading: boolean;
93 searchCommunityOptions: Choice[];
94 searchPersonLoading: boolean;
95 searchPersonOptions: Choice[];
98 type FilterType = "user" | "community";
107 filterType: FilterType;
109 onSearch: (text: string) => void;
110 onChange: (choice: Choice) => void;
113 <div className="mb-3 row">
115 className="col-md-4 col-form-label"
116 htmlFor={`block-${filterType}-filter`}
118 {I18NextService.i18n.t(`block_${filterType}` as NoOptionI18nKeys)}
120 <div className="col-md-8">
122 id={`block-${filterType}-filter`}
124 { label: emDash, value: "0", disabled: true } as Choice,
134 export class Settings extends Component<any, SettingsState> {
135 private isoData = setIsoData(this.context);
136 state: SettingsState = {
137 saveRes: { state: "empty" },
138 deleteAccountRes: { state: "empty" },
139 changePasswordRes: { state: "empty" },
140 saveUserSettingsForm: {},
141 changePasswordForm: {},
142 deleteAccountShowConfirm: false,
143 deleteAccountForm: {},
146 currentTab: "settings",
147 siteRes: this.isoData.site_res,
149 searchCommunityLoading: false,
150 searchCommunityOptions: [],
151 searchPersonLoading: false,
152 searchPersonOptions: [],
155 constructor(props: any, context: any) {
156 super(props, context);
158 this.handleSortTypeChange = this.handleSortTypeChange.bind(this);
159 this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
160 this.handleBioChange = this.handleBioChange.bind(this);
161 this.handleDiscussionLanguageChange =
162 this.handleDiscussionLanguageChange.bind(this);
164 this.handleAvatarUpload = this.handleAvatarUpload.bind(this);
165 this.handleAvatarRemove = this.handleAvatarRemove.bind(this);
167 this.handleBannerUpload = this.handleBannerUpload.bind(this);
168 this.handleBannerRemove = this.handleBannerRemove.bind(this);
169 this.userSettings = this.userSettings.bind(this);
170 this.blockCards = this.blockCards.bind(this);
172 this.handleBlockPerson = this.handleBlockPerson.bind(this);
173 this.handleBlockCommunity = this.handleBlockCommunity.bind(this);
175 const mui = UserService.Instance.myUserInfo;
182 default_listing_type,
188 show_new_post_notifs,
189 send_notifications_to_email,
200 } = mui.local_user_view;
204 personBlocks: mui.person_blocks,
205 communityBlocks: mui.community_blocks,
206 saveUserSettingsForm: {
207 ...this.state.saveUserSettingsForm,
209 theme: theme ?? "browser",
211 default_listing_type,
213 discussion_languages: mui.discussion_languages,
222 show_new_post_notifs,
225 send_notifications_to_email,
232 async componentDidMount() {
234 this.setState({ themeList: await fetchThemeList() });
237 get documentTitle(): string {
238 return I18NextService.i18n.t("settings");
243 <div className="person-settings container-lg">
245 title={this.documentTitle}
246 path={this.context.router.route.match.url}
247 description={this.documentTitle}
248 image={this.state.saveUserSettingsForm.avatar}
254 label: I18NextService.i18n.t("settings"),
255 getNode: this.userSettings,
259 label: I18NextService.i18n.t("blocks"),
260 getNode: this.blockCards,
268 userSettings(isSelected: boolean) {
271 className={classNames("tab-pane show", {
275 id="settings-tab-pane"
277 <div className="row">
278 <div className="col-12 col-md-6">
279 <div className="card border-secondary mb-3">
280 <div className="card-body">{this.saveUserSettingsHtmlForm()}</div>
283 <div className="col-12 col-md-6">
284 <div className="card border-secondary mb-3">
285 <div className="card-body">{this.changePasswordHtmlForm()}</div>
293 blockCards(isSelected: boolean) {
296 className={classNames("tab-pane", {
302 <div className="row">
303 <div className="col-12 col-md-6">
304 <div className="card border-secondary mb-3">
305 <div className="card-body">{this.blockUserCard()}</div>
308 <div className="col-12 col-md-6">
309 <div className="card border-secondary mb-3">
310 <div className="card-body">{this.blockCommunityCard()}</div>
318 changePasswordHtmlForm() {
321 <h2 className="h5">{I18NextService.i18n.t("change_password")}</h2>
322 <form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
323 <div className="mb-3">
326 value={this.state.changePasswordForm.new_password}
327 onInput={linkEvent(this, this.handleNewPasswordChange)}
329 label={I18NextService.i18n.t("new_password")}
333 <div className="mb-3">
335 id="verify-new-password"
336 value={this.state.changePasswordForm.new_password_verify}
337 onInput={linkEvent(this, this.handleNewPasswordVerifyChange)}
338 label={I18NextService.i18n.t("verify_password")}
342 <div className="mb-3">
344 id="user-old-password"
345 value={this.state.changePasswordForm.old_password}
346 onInput={linkEvent(this, this.handleOldPasswordChange)}
347 label={I18NextService.i18n.t("old_password")}
350 <div className="input-group mb-3">
353 className="btn d-block btn-secondary me-4 w-100"
355 {this.state.changePasswordRes.state === "loading" ? (
358 capitalizeFirstLetter(I18NextService.i18n.t("save"))
368 const { searchPersonLoading, searchPersonOptions } = this.state;
374 loading={searchPersonLoading}
375 onChange={this.handleBlockPerson}
376 onSearch={this.handlePersonSearch}
377 options={searchPersonOptions}
379 {this.blockedUsersList()}
387 <h2 className="h5">{I18NextService.i18n.t("blocked_users")}</h2>
388 <ul className="list-unstyled mb-0">
389 {this.state.personBlocks.map(pb => (
390 <li key={pb.target.id}>
392 <PersonListing person={pb.target} />
394 className="btn btn-sm"
396 { ctx: this, recipientId: pb.target.id },
397 this.handleUnblockPerson,
399 data-tippy-content={I18NextService.i18n.t("unblock_user")}
401 <Icon icon="x" classes="icon-inline" />
411 blockCommunityCard() {
412 const { searchCommunityLoading, searchCommunityOptions } = this.state;
417 filterType="community"
418 loading={searchCommunityLoading}
419 onChange={this.handleBlockCommunity}
420 onSearch={this.handleCommunitySearch}
421 options={searchCommunityOptions}
423 {this.blockedCommunitiesList()}
428 blockedCommunitiesList() {
431 <h2 className="h5">{I18NextService.i18n.t("blocked_communities")}</h2>
432 <ul className="list-unstyled mb-0">
433 {this.state.communityBlocks.map(cb => (
434 <li key={cb.community.id}>
436 <CommunityLink community={cb.community} />
438 className="btn btn-sm"
440 { ctx: this, communityId: cb.community.id },
441 this.handleUnblockCommunity,
443 data-tippy-content={I18NextService.i18n.t(
447 <Icon icon="x" classes="icon-inline" />
457 saveUserSettingsHtmlForm() {
458 const selectedLangs = this.state.saveUserSettingsForm.discussion_languages;
462 <h2 className="h5">{I18NextService.i18n.t("settings")}</h2>
463 <form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}>
464 <div className="mb-3 row">
465 <label className="col-sm-3 col-form-label" htmlFor="display-name">
466 {I18NextService.i18n.t("display_name")}
468 <div className="col-sm-9">
472 className="form-control"
473 placeholder={I18NextService.i18n.t("optional")}
474 value={this.state.saveUserSettingsForm.display_name}
475 onInput={linkEvent(this, this.handleDisplayNameChange)}
476 pattern="^(?!@)(.+)$"
481 <div className="mb-3 row">
482 <label className="col-sm-3 col-form-label" htmlFor="user-bio">
483 {I18NextService.i18n.t("bio")}
485 <div className="col-sm-9">
487 initialContent={this.state.saveUserSettingsForm.bio}
488 onContentChange={this.handleBioChange}
490 hideNavigationWarnings
491 allLanguages={this.state.siteRes.all_languages}
492 siteLanguages={this.state.siteRes.discussion_languages}
496 <div className="mb-3 row">
497 <label className="col-sm-3 col-form-label" htmlFor="user-email">
498 {I18NextService.i18n.t("email")}
500 <div className="col-sm-9">
504 className="form-control"
505 placeholder={I18NextService.i18n.t("optional")}
506 value={this.state.saveUserSettingsForm.email}
507 onInput={linkEvent(this, this.handleEmailChange)}
512 <div className="mb-3 row">
513 <label className="col-sm-3 col-form-label" htmlFor="matrix-user-id">
514 <a href={elementUrl} rel={relTags}>
515 {I18NextService.i18n.t("matrix_user_id")}
518 <div className="col-sm-9">
522 className="form-control"
523 placeholder="@user:example.com"
524 value={this.state.saveUserSettingsForm.matrix_user_id}
525 onInput={linkEvent(this, this.handleMatrixUserIdChange)}
526 pattern="^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
530 <div className="mb-3 row">
531 <label className="col-sm-3 col-form-label">
532 {I18NextService.i18n.t("avatar")}
534 <div className="col-sm-9">
536 uploadTitle={I18NextService.i18n.t("upload_avatar")}
537 imageSrc={this.state.saveUserSettingsForm.avatar}
538 onUpload={this.handleAvatarUpload}
539 onRemove={this.handleAvatarRemove}
544 <div className="mb-3 row">
545 <label className="col-sm-3 col-form-label">
546 {I18NextService.i18n.t("banner")}
548 <div className="col-sm-9">
550 uploadTitle={I18NextService.i18n.t("upload_banner")}
551 imageSrc={this.state.saveUserSettingsForm.banner}
552 onUpload={this.handleBannerUpload}
553 onRemove={this.handleBannerRemove}
557 <div className="mb-3 row">
558 <label className="col-sm-3 form-label" htmlFor="user-language">
559 {I18NextService.i18n.t("interface_language")}
561 <div className="col-sm-9">
564 value={this.state.saveUserSettingsForm.interface_language}
565 onChange={linkEvent(this, this.handleInterfaceLangChange)}
566 className="form-select d-inline-block w-auto"
568 <option disabled aria-hidden="true">
569 {I18NextService.i18n.t("interface_language")}
571 <option value="browser">
572 {I18NextService.i18n.t("browser_default")}
574 <option disabled aria-hidden="true">
578 .sort((a, b) => a.code.localeCompare(b.code))
580 <option key={lang.code} value={lang.code}>
588 allLanguages={this.state.siteRes.all_languages}
589 siteLanguages={this.state.siteRes.discussion_languages}
590 selectedLanguageIds={selectedLangs}
592 showLanguageWarning={true}
595 onChange={this.handleDiscussionLanguageChange}
597 <div className="mb-3 row">
598 <label className="col-sm-3 col-form-label" htmlFor="user-theme">
599 {I18NextService.i18n.t("theme")}
601 <div className="col-sm-9">
604 value={this.state.saveUserSettingsForm.theme}
605 onChange={linkEvent(this, this.handleThemeChange)}
606 className="form-select d-inline-block w-auto"
608 <option disabled aria-hidden="true">
609 {I18NextService.i18n.t("theme")}
611 <option value="browser">
612 {I18NextService.i18n.t("browser_default")}
614 <option value="browser-compact">
615 {I18NextService.i18n.t("browser_default_compact")}
617 {this.state.themeList.map(theme => (
618 <option key={theme} value={theme}>
625 <form className="mb-3 row">
626 <label className="col-sm-3 col-form-label">
627 {I18NextService.i18n.t("type")}
629 <div className="col-sm-9">
632 this.state.saveUserSettingsForm.default_listing_type ??
635 showLocal={showLocal(this.isoData)}
637 onChange={this.handleListingTypeChange}
641 <form className="mb-3 row">
642 <label className="col-sm-3 col-form-label">
643 {I18NextService.i18n.t("sort_type")}
645 <div className="col-sm-9">
648 this.state.saveUserSettingsForm.default_sort_type ?? "Active"
650 onChange={this.handleSortTypeChange}
654 <div className="input-group mb-3">
655 <div className="form-check">
657 className="form-check-input"
660 checked={this.state.saveUserSettingsForm.show_nsfw}
661 onChange={linkEvent(this, this.handleShowNsfwChange)}
663 <label className="form-check-label" htmlFor="user-show-nsfw">
664 {I18NextService.i18n.t("show_nsfw")}
668 <div className="input-group mb-3">
669 <div className="form-check">
671 className="form-check-input"
672 id="user-show-scores"
674 checked={this.state.saveUserSettingsForm.show_scores}
675 onChange={linkEvent(this, this.handleShowScoresChange)}
677 <label className="form-check-label" htmlFor="user-show-scores">
678 {I18NextService.i18n.t("show_scores")}
682 <div className="input-group mb-3">
683 <div className="form-check">
685 className="form-check-input"
686 id="user-show-avatars"
688 checked={this.state.saveUserSettingsForm.show_avatars}
689 onChange={linkEvent(this, this.handleShowAvatarsChange)}
691 <label className="form-check-label" htmlFor="user-show-avatars">
692 {I18NextService.i18n.t("show_avatars")}
696 <div className="input-group mb-3">
697 <div className="form-check">
699 className="form-check-input"
700 id="user-bot-account"
702 checked={this.state.saveUserSettingsForm.bot_account}
703 onChange={linkEvent(this, this.handleBotAccount)}
705 <label className="form-check-label" htmlFor="user-bot-account">
706 {I18NextService.i18n.t("bot_account")}
710 <div className="input-group mb-3">
711 <div className="form-check">
713 className="form-check-input"
714 id="user-show-bot-accounts"
716 checked={this.state.saveUserSettingsForm.show_bot_accounts}
717 onChange={linkEvent(this, this.handleShowBotAccounts)}
720 className="form-check-label"
721 htmlFor="user-show-bot-accounts"
723 {I18NextService.i18n.t("show_bot_accounts")}
727 <div className="input-group mb-3">
728 <div className="form-check">
730 className="form-check-input"
731 id="user-show-read-posts"
733 checked={this.state.saveUserSettingsForm.show_read_posts}
734 onChange={linkEvent(this, this.handleReadPosts)}
737 className="form-check-label"
738 htmlFor="user-show-read-posts"
740 {I18NextService.i18n.t("show_read_posts")}
744 <div className="input-group mb-3">
745 <div className="form-check">
747 className="form-check-input"
748 id="user-show-new-post-notifs"
750 checked={this.state.saveUserSettingsForm.show_new_post_notifs}
751 onChange={linkEvent(this, this.handleShowNewPostNotifs)}
754 className="form-check-label"
755 htmlFor="user-show-new-post-notifs"
757 {I18NextService.i18n.t("show_new_post_notifs")}
761 <div className="input-group mb-3">
762 <div className="form-check">
764 className="form-check-input"
765 id="user-send-notifications-to-email"
767 disabled={!this.state.saveUserSettingsForm.email}
769 this.state.saveUserSettingsForm.send_notifications_to_email
773 this.handleSendNotificationsToEmailChange,
777 className="form-check-label"
778 htmlFor="user-send-notifications-to-email"
780 {I18NextService.i18n.t("send_notifications_to_email")}
784 <div className="input-group mb-3">
785 <div className="form-check">
787 className="form-check-input"
788 id="user-open-links-in-new-tab"
790 checked={this.state.saveUserSettingsForm.open_links_in_new_tab}
791 onChange={linkEvent(this, this.handleOpenInNewTab)}
794 className="form-check-label"
795 htmlFor="user-open-links-in-new-tab"
797 {I18NextService.i18n.t("open_links_in_new_tab")}
802 <div className="input-group mb-3">
803 <button type="submit" className="btn d-block btn-secondary me-4">
804 {this.state.saveRes.state === "loading" ? (
807 capitalizeFirstLetter(I18NextService.i18n.t("save"))
814 onSubmit={linkEvent(this, this.handleDeleteAccount)}
818 className="btn d-block btn-danger"
821 this.handleDeleteAccountShowConfirmToggle,
824 {I18NextService.i18n.t("delete_account")}
826 {this.state.deleteAccountShowConfirm && (
829 className="my-2 alert alert-danger d-block"
831 htmlFor="password-delete-account"
833 {I18NextService.i18n.t("delete_account_confirm")}
836 id="password-delete-account"
837 value={this.state.deleteAccountForm.password}
840 this.handleDeleteAccountPasswordChange,
846 className="btn btn-danger me-4"
847 disabled={!this.state.deleteAccountForm.password}
849 {this.state.deleteAccountRes.state === "loading" ? (
852 capitalizeFirstLetter(I18NextService.i18n.t("delete"))
856 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 handleOpenInNewTab(i: Settings, event: any) {
1053 (s.saveUserSettingsForm.open_links_in_new_tab = event.target.checked), s
1058 handleShowScoresChange(i: Settings, event: any) {
1059 const mui = UserService.Instance.myUserInfo;
1061 mui.local_user_view.local_user.show_scores = event.target.checked;
1064 s => ((s.saveUserSettingsForm.show_scores = event.target.checked), s),
1068 handleGenerateTotp(i: Settings, event: any) {
1069 // Coerce false to undefined here, so it won't generate it.
1070 const checked: boolean | undefined = event.target.checked || undefined;
1072 toast(I18NextService.i18n.t("two_factor_setup_instructions"));
1074 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1077 handleRemoveTotp(i: Settings, event: any) {
1078 // Coerce true to undefined here, so it won't generate it.
1079 const checked: boolean | undefined = !event.target.checked && undefined;
1080 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1083 handleSendNotificationsToEmailChange(i: Settings, event: any) {
1086 (s.saveUserSettingsForm.send_notifications_to_email =
1087 event.target.checked),
1093 handleThemeChange(i: Settings, event: any) {
1094 i.setState(s => ((s.saveUserSettingsForm.theme = event.target.value), s));
1095 setTheme(event.target.value, true);
1098 handleInterfaceLangChange(i: Settings, event: any) {
1099 const newLang = event.target.value ?? "browser";
1100 I18NextService.i18n.changeLanguage(
1101 newLang === "browser" ? navigator.languages : newLang,
1106 (s.saveUserSettingsForm.interface_language = event.target.value), s
1111 handleDiscussionLanguageChange(val: number[]) {
1113 s => ((s.saveUserSettingsForm.discussion_languages = val), s),
1117 handleSortTypeChange(val: SortType) {
1118 this.setState(s => ((s.saveUserSettingsForm.default_sort_type = val), s));
1121 handleListingTypeChange(val: ListingType) {
1123 s => ((s.saveUserSettingsForm.default_listing_type = val), s),
1127 handleEmailChange(i: Settings, event: any) {
1128 i.setState(s => ((s.saveUserSettingsForm.email = event.target.value), s));
1131 handleBioChange(val: string) {
1132 this.setState(s => ((s.saveUserSettingsForm.bio = val), s));
1135 handleAvatarUpload(url: string) {
1136 this.setState(s => ((s.saveUserSettingsForm.avatar = url), s));
1139 handleAvatarRemove() {
1140 this.setState(s => ((s.saveUserSettingsForm.avatar = ""), s));
1143 handleBannerUpload(url: string) {
1144 this.setState(s => ((s.saveUserSettingsForm.banner = url), s));
1147 handleBannerRemove() {
1148 this.setState(s => ((s.saveUserSettingsForm.banner = ""), s));
1151 handleDisplayNameChange(i: Settings, event: any) {
1153 s => ((s.saveUserSettingsForm.display_name = event.target.value), s),
1157 handleMatrixUserIdChange(i: Settings, event: any) {
1159 s => ((s.saveUserSettingsForm.matrix_user_id = event.target.value), s),
1163 handleNewPasswordChange(i: Settings, event: any) {
1164 const newPass: string | undefined =
1165 event.target.value === "" ? undefined : event.target.value;
1166 i.setState(s => ((s.changePasswordForm.new_password = newPass), s));
1169 handleNewPasswordVerifyChange(i: Settings, event: any) {
1170 const newPassVerify: string | undefined =
1171 event.target.value === "" ? undefined : event.target.value;
1173 s => ((s.changePasswordForm.new_password_verify = newPassVerify), s),
1177 handleOldPasswordChange(i: Settings, event: any) {
1178 const oldPass: string | undefined =
1179 event.target.value === "" ? undefined : event.target.value;
1180 i.setState(s => ((s.changePasswordForm.old_password = oldPass), s));
1183 async handleSaveSettingsSubmit(i: Settings, event: any) {
1184 event.preventDefault();
1185 i.setState({ saveRes: { state: "loading" } });
1187 const saveRes = await HttpService.client.saveUserSettings({
1188 ...i.state.saveUserSettingsForm,
1189 auth: myAuthRequired(),
1192 if (saveRes.state === "success") {
1193 UserService.Instance.login({
1197 toast(I18NextService.i18n.t("saved"));
1198 window.scrollTo(0, 0);
1201 i.setState({ saveRes });
1204 async handleChangePasswordSubmit(i: Settings, event: any) {
1205 event.preventDefault();
1206 const { new_password, new_password_verify, old_password } =
1207 i.state.changePasswordForm;
1209 if (new_password && old_password && new_password_verify) {
1210 i.setState({ changePasswordRes: { state: "loading" } });
1211 const changePasswordRes = await HttpService.client.changePassword({
1213 new_password_verify,
1215 auth: myAuthRequired(),
1217 if (changePasswordRes.state === "success") {
1218 UserService.Instance.login({
1219 res: changePasswordRes.data,
1222 window.scrollTo(0, 0);
1223 toast(I18NextService.i18n.t("password_changed"));
1226 i.setState({ changePasswordRes });
1230 handleDeleteAccountShowConfirmToggle(i: Settings) {
1231 i.setState({ deleteAccountShowConfirm: !i.state.deleteAccountShowConfirm });
1234 handleDeleteAccountPasswordChange(i: Settings, event: any) {
1235 i.setState(s => ((s.deleteAccountForm.password = event.target.value), s));
1238 async handleDeleteAccount(i: Settings, event: Event) {
1239 event.preventDefault();
1240 const password = i.state.deleteAccountForm.password;
1242 i.setState({ deleteAccountRes: { state: "loading" } });
1243 const deleteAccountRes = await HttpService.client.deleteAccount({
1245 auth: myAuthRequired(),
1247 if (deleteAccountRes.state === "success") {
1248 UserService.Instance.logout();
1249 this.context.router.history.replace("/");
1252 i.setState({ deleteAccountRes });
1256 handleSwitchTab(i: { ctx: Settings; tab: string }) {
1257 i.ctx.setState({ currentTab: i.tab });
1260 personBlock(res: RequestState<BlockPersonResponse>) {
1261 if (res.state === "success") {
1262 updatePersonBlock(res.data);
1263 const mui = UserService.Instance.myUserInfo;
1265 this.setState({ personBlocks: mui.person_blocks });
1270 communityBlock(res: RequestState<BlockCommunityResponse>) {
1271 if (res.state === "success") {
1272 updateCommunityBlock(res.data);
1273 const mui = UserService.Instance.myUserInfo;
1275 this.setState({ communityBlocks: mui.community_blocks });