]> Untitled Git - lemmy-ui.git/blob - src/shared/components/person/settings.tsx
Removing monads. Fixes #884 (#886)
[lemmy-ui.git] / src / shared / components / person / settings.tsx
1 import { Component, linkEvent } from "inferno";
2 import {
3   BlockCommunity,
4   BlockCommunityResponse,
5   BlockPerson,
6   BlockPersonResponse,
7   ChangePassword,
8   CommunityBlockView,
9   CommunityView,
10   DeleteAccount,
11   GetSiteResponse,
12   ListingType,
13   LoginResponse,
14   PersonBlockView,
15   PersonViewSafe,
16   SaveUserSettings,
17   SortType,
18   UserOperation,
19   wsJsonToRes,
20   wsUserOp,
21 } from "lemmy-js-client";
22 import { Subscription } from "rxjs";
23 import { i18n, languages } from "../../i18next";
24 import { UserService, WebSocketService } from "../../services";
25 import {
26   capitalizeFirstLetter,
27   choicesConfig,
28   communitySelectName,
29   communityToChoice,
30   debounce,
31   elementUrl,
32   enableNsfw,
33   fetchCommunities,
34   fetchThemeList,
35   fetchUsers,
36   getLanguages,
37   isBrowser,
38   myAuth,
39   personSelectName,
40   personToChoice,
41   relTags,
42   setIsoData,
43   setTheme,
44   setupTippy,
45   showLocal,
46   toast,
47   updateCommunityBlock,
48   updatePersonBlock,
49   wsClient,
50   wsSubscribe,
51 } from "../../utils";
52 import { HtmlTags } from "../common/html-tags";
53 import { Icon, Spinner } from "../common/icon";
54 import { ImageUploadForm } from "../common/image-upload-form";
55 import { LanguageSelect } from "../common/language-select";
56 import { ListingTypeSelect } from "../common/listing-type-select";
57 import { MarkdownTextArea } from "../common/markdown-textarea";
58 import { SortSelect } from "../common/sort-select";
59 import { CommunityLink } from "../community/community-link";
60 import { PersonListing } from "./person-listing";
61
62 var Choices: any;
63 if (isBrowser()) {
64   Choices = require("choices.js");
65 }
66
67 interface SettingsState {
68   // TODO redo these forms
69   saveUserSettingsForm: {
70     show_nsfw?: boolean;
71     theme?: string;
72     default_sort_type?: number;
73     default_listing_type?: number;
74     interface_language?: string;
75     avatar?: string;
76     banner?: string;
77     display_name?: string;
78     email?: string;
79     bio?: string;
80     matrix_user_id?: string;
81     show_avatars?: boolean;
82     show_scores?: boolean;
83     send_notifications_to_email?: boolean;
84     bot_account?: boolean;
85     show_bot_accounts?: boolean;
86     show_read_posts?: boolean;
87     show_new_post_notifs?: boolean;
88     discussion_languages?: number[];
89   };
90   changePasswordForm: {
91     new_password?: string;
92     new_password_verify?: string;
93     old_password?: string;
94   };
95   deleteAccountForm: {
96     password?: string;
97   };
98   personBlocks: PersonBlockView[];
99   blockPerson?: PersonViewSafe;
100   communityBlocks: CommunityBlockView[];
101   blockCommunityId: number;
102   blockCommunity?: CommunityView;
103   currentTab: string;
104   themeList: string[];
105   saveUserSettingsLoading: boolean;
106   changePasswordLoading: boolean;
107   deleteAccountLoading: boolean;
108   deleteAccountShowConfirm: boolean;
109   siteRes: GetSiteResponse;
110 }
111
112 export class Settings extends Component<any, SettingsState> {
113   private isoData = setIsoData(this.context);
114   private blockPersonChoices: any;
115   private blockCommunityChoices: any;
116   private subscription?: Subscription;
117   state: SettingsState = {
118     saveUserSettingsForm: {},
119     changePasswordForm: {},
120     saveUserSettingsLoading: false,
121     changePasswordLoading: false,
122     deleteAccountLoading: false,
123     deleteAccountShowConfirm: false,
124     deleteAccountForm: {},
125     personBlocks: [],
126     communityBlocks: [],
127     blockCommunityId: 0,
128     currentTab: "settings",
129     siteRes: this.isoData.site_res,
130     themeList: [],
131   };
132
133   constructor(props: any, context: any) {
134     super(props, context);
135
136     this.handleSortTypeChange = this.handleSortTypeChange.bind(this);
137     this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
138     this.handleBioChange = this.handleBioChange.bind(this);
139     this.handleDiscussionLanguageChange =
140       this.handleDiscussionLanguageChange.bind(this);
141
142     this.handleAvatarUpload = this.handleAvatarUpload.bind(this);
143     this.handleAvatarRemove = this.handleAvatarRemove.bind(this);
144
145     this.handleBannerUpload = this.handleBannerUpload.bind(this);
146     this.handleBannerRemove = this.handleBannerRemove.bind(this);
147
148     this.parseMessage = this.parseMessage.bind(this);
149     this.subscription = wsSubscribe(this.parseMessage);
150
151     let mui = UserService.Instance.myUserInfo;
152     if (mui) {
153       let luv = mui.local_user_view;
154       this.state = {
155         ...this.state,
156         personBlocks: mui.person_blocks,
157         communityBlocks: mui.community_blocks,
158         saveUserSettingsForm: {
159           ...this.state.saveUserSettingsForm,
160           show_nsfw: luv.local_user.show_nsfw,
161           theme: luv.local_user.theme ? luv.local_user.theme : "browser",
162           default_sort_type: luv.local_user.default_sort_type,
163           default_listing_type: luv.local_user.default_listing_type,
164           interface_language: luv.local_user.interface_language,
165           discussion_languages: mui.discussion_languages,
166           avatar: luv.person.avatar,
167           banner: luv.person.banner,
168           display_name: luv.person.display_name,
169           show_avatars: luv.local_user.show_avatars,
170           bot_account: luv.person.bot_account,
171           show_bot_accounts: luv.local_user.show_bot_accounts,
172           show_scores: luv.local_user.show_scores,
173           show_read_posts: luv.local_user.show_read_posts,
174           show_new_post_notifs: luv.local_user.show_new_post_notifs,
175           email: luv.local_user.email,
176           bio: luv.person.bio,
177           send_notifications_to_email:
178             luv.local_user.send_notifications_to_email,
179           matrix_user_id: luv.person.matrix_user_id,
180         },
181       };
182     }
183   }
184
185   async componentDidMount() {
186     setupTippy();
187     this.setState({ themeList: await fetchThemeList() });
188   }
189
190   componentWillUnmount() {
191     this.subscription?.unsubscribe();
192   }
193
194   get documentTitle(): string {
195     return i18n.t("settings");
196   }
197
198   render() {
199     return (
200       <div className="container-lg">
201         <>
202           <HtmlTags
203             title={this.documentTitle}
204             path={this.context.router.route.match.url}
205             description={this.documentTitle}
206             image={this.state.saveUserSettingsForm.avatar}
207           />
208           <ul className="nav nav-tabs mb-2">
209             <li className="nav-item">
210               <button
211                 className={`nav-link btn ${
212                   this.state.currentTab == "settings" && "active"
213                 }`}
214                 onClick={linkEvent(
215                   { ctx: this, tab: "settings" },
216                   this.handleSwitchTab
217                 )}
218               >
219                 {i18n.t("settings")}
220               </button>
221             </li>
222             <li className="nav-item">
223               <button
224                 className={`nav-link btn ${
225                   this.state.currentTab == "blocks" && "active"
226                 }`}
227                 onClick={linkEvent(
228                   { ctx: this, tab: "blocks" },
229                   this.handleSwitchTab
230                 )}
231               >
232                 {i18n.t("blocks")}
233               </button>
234             </li>
235           </ul>
236           {this.state.currentTab == "settings" && this.userSettings()}
237           {this.state.currentTab == "blocks" && this.blockCards()}
238         </>
239       </div>
240     );
241   }
242
243   userSettings() {
244     return (
245       <div className="row">
246         <div className="col-12 col-md-6">
247           <div className="card border-secondary mb-3">
248             <div className="card-body">{this.saveUserSettingsHtmlForm()}</div>
249           </div>
250         </div>
251         <div className="col-12 col-md-6">
252           <div className="card border-secondary mb-3">
253             <div className="card-body">{this.changePasswordHtmlForm()}</div>
254           </div>
255         </div>
256       </div>
257     );
258   }
259
260   blockCards() {
261     return (
262       <div className="row">
263         <div className="col-12 col-md-6">
264           <div className="card border-secondary mb-3">
265             <div className="card-body">{this.blockUserCard()}</div>
266           </div>
267         </div>
268         <div className="col-12 col-md-6">
269           <div className="card border-secondary mb-3">
270             <div className="card-body">{this.blockCommunityCard()}</div>
271           </div>
272         </div>
273       </div>
274     );
275   }
276
277   changePasswordHtmlForm() {
278     return (
279       <>
280         <h5>{i18n.t("change_password")}</h5>
281         <form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
282           <div className="form-group row">
283             <label className="col-sm-5 col-form-label" htmlFor="user-password">
284               {i18n.t("new_password")}
285             </label>
286             <div className="col-sm-7">
287               <input
288                 type="password"
289                 id="user-password"
290                 className="form-control"
291                 value={this.state.changePasswordForm.new_password}
292                 autoComplete="new-password"
293                 maxLength={60}
294                 onInput={linkEvent(this, this.handleNewPasswordChange)}
295               />
296             </div>
297           </div>
298           <div className="form-group row">
299             <label
300               className="col-sm-5 col-form-label"
301               htmlFor="user-verify-password"
302             >
303               {i18n.t("verify_password")}
304             </label>
305             <div className="col-sm-7">
306               <input
307                 type="password"
308                 id="user-verify-password"
309                 className="form-control"
310                 value={this.state.changePasswordForm.new_password_verify}
311                 autoComplete="new-password"
312                 maxLength={60}
313                 onInput={linkEvent(this, this.handleNewPasswordVerifyChange)}
314               />
315             </div>
316           </div>
317           <div className="form-group row">
318             <label
319               className="col-sm-5 col-form-label"
320               htmlFor="user-old-password"
321             >
322               {i18n.t("old_password")}
323             </label>
324             <div className="col-sm-7">
325               <input
326                 type="password"
327                 id="user-old-password"
328                 className="form-control"
329                 value={this.state.changePasswordForm.old_password}
330                 autoComplete="new-password"
331                 maxLength={60}
332                 onInput={linkEvent(this, this.handleOldPasswordChange)}
333               />
334             </div>
335           </div>
336           <div className="form-group">
337             <button type="submit" className="btn btn-block btn-secondary mr-4">
338               {this.state.changePasswordLoading ? (
339                 <Spinner />
340               ) : (
341                 capitalizeFirstLetter(i18n.t("save"))
342               )}
343             </button>
344           </div>
345         </form>
346       </>
347     );
348   }
349
350   blockUserCard() {
351     return (
352       <div>
353         {this.blockUserForm()}
354         {this.blockedUsersList()}
355       </div>
356     );
357   }
358
359   blockedUsersList() {
360     return (
361       <>
362         <h5>{i18n.t("blocked_users")}</h5>
363         <ul className="list-unstyled mb-0">
364           {this.state.personBlocks.map(pb => (
365             <li key={pb.target.id}>
366               <span>
367                 <PersonListing person={pb.target} />
368                 <button
369                   className="btn btn-sm"
370                   onClick={linkEvent(
371                     { ctx: this, recipientId: pb.target.id },
372                     this.handleUnblockPerson
373                   )}
374                   data-tippy-content={i18n.t("unblock_user")}
375                 >
376                   <Icon icon="x" classes="icon-inline" />
377                 </button>
378               </span>
379             </li>
380           ))}
381         </ul>
382       </>
383     );
384   }
385
386   blockUserForm() {
387     let blockPerson = this.state.blockPerson;
388     return (
389       <div className="form-group row">
390         <label
391           className="col-md-4 col-form-label"
392           htmlFor="block-person-filter"
393         >
394           {i18n.t("block_user")}
395         </label>
396         <div className="col-md-8">
397           <select
398             className="form-control"
399             id="block-person-filter"
400             value={blockPerson?.person.id ?? 0}
401           >
402             <option value="0">—</option>
403             {blockPerson && (
404               <option value={blockPerson.person.id}>
405                 {personSelectName(blockPerson)}
406               </option>
407             )}
408           </select>
409         </div>
410       </div>
411     );
412   }
413
414   blockCommunityCard() {
415     return (
416       <div>
417         {this.blockCommunityForm()}
418         {this.blockedCommunitiesList()}
419       </div>
420     );
421   }
422
423   blockedCommunitiesList() {
424     return (
425       <>
426         <h5>{i18n.t("blocked_communities")}</h5>
427         <ul className="list-unstyled mb-0">
428           {this.state.communityBlocks.map(cb => (
429             <li key={cb.community.id}>
430               <span>
431                 <CommunityLink community={cb.community} />
432                 <button
433                   className="btn btn-sm"
434                   onClick={linkEvent(
435                     { ctx: this, communityId: cb.community.id },
436                     this.handleUnblockCommunity
437                   )}
438                   data-tippy-content={i18n.t("unblock_community")}
439                 >
440                   <Icon icon="x" classes="icon-inline" />
441                 </button>
442               </span>
443             </li>
444           ))}
445         </ul>
446       </>
447     );
448   }
449
450   blockCommunityForm() {
451     return (
452       <div className="form-group row">
453         <label
454           className="col-md-4 col-form-label"
455           htmlFor="block-community-filter"
456         >
457           {i18n.t("block_community")}
458         </label>
459         <div className="col-md-8">
460           <select
461             className="form-control"
462             id="block-community-filter"
463             value={this.state.blockCommunityId}
464           >
465             <option value="0">—</option>
466             {this.state.blockCommunity && (
467               <option value={this.state.blockCommunity.community.id}>
468                 {communitySelectName(this.state.blockCommunity)}
469               </option>
470             )}
471           </select>
472         </div>
473       </div>
474     );
475   }
476
477   saveUserSettingsHtmlForm() {
478     let selectedLangs = this.state.saveUserSettingsForm.discussion_languages;
479
480     return (
481       <>
482         <h5>{i18n.t("settings")}</h5>
483         <form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}>
484           <div className="form-group row">
485             <label className="col-sm-5 col-form-label" htmlFor="display-name">
486               {i18n.t("display_name")}
487             </label>
488             <div className="col-sm-7">
489               <input
490                 id="display-name"
491                 type="text"
492                 className="form-control"
493                 placeholder={i18n.t("optional")}
494                 value={this.state.saveUserSettingsForm.display_name}
495                 onInput={linkEvent(this, this.handleDisplayNameChange)}
496                 pattern="^(?!@)(.+)$"
497                 minLength={3}
498               />
499             </div>
500           </div>
501           <div className="form-group row">
502             <label className="col-sm-3 col-form-label" htmlFor="user-bio">
503               {i18n.t("bio")}
504             </label>
505             <div className="col-sm-9">
506               <MarkdownTextArea
507                 initialContent={this.state.saveUserSettingsForm.bio}
508                 onContentChange={this.handleBioChange}
509                 maxLength={300}
510                 hideNavigationWarnings
511                 allLanguages={this.state.siteRes.all_languages}
512                 siteLanguages={this.state.siteRes.discussion_languages}
513               />
514             </div>
515           </div>
516           <div className="form-group row">
517             <label className="col-sm-3 col-form-label" htmlFor="user-email">
518               {i18n.t("email")}
519             </label>
520             <div className="col-sm-9">
521               <input
522                 type="email"
523                 id="user-email"
524                 className="form-control"
525                 placeholder={i18n.t("optional")}
526                 value={this.state.saveUserSettingsForm.email}
527                 onInput={linkEvent(this, this.handleEmailChange)}
528                 minLength={3}
529               />
530             </div>
531           </div>
532           <div className="form-group row">
533             <label className="col-sm-5 col-form-label" htmlFor="matrix-user-id">
534               <a href={elementUrl} rel={relTags}>
535                 {i18n.t("matrix_user_id")}
536               </a>
537             </label>
538             <div className="col-sm-7">
539               <input
540                 id="matrix-user-id"
541                 type="text"
542                 className="form-control"
543                 placeholder="@user:example.com"
544                 value={this.state.saveUserSettingsForm.matrix_user_id}
545                 onInput={linkEvent(this, this.handleMatrixUserIdChange)}
546                 pattern="^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
547               />
548             </div>
549           </div>
550           <div className="form-group row">
551             <label className="col-sm-3">{i18n.t("avatar")}</label>
552             <div className="col-sm-9">
553               <ImageUploadForm
554                 uploadTitle={i18n.t("upload_avatar")}
555                 imageSrc={this.state.saveUserSettingsForm.avatar}
556                 onUpload={this.handleAvatarUpload}
557                 onRemove={this.handleAvatarRemove}
558                 rounded
559               />
560             </div>
561           </div>
562           <div className="form-group row">
563             <label className="col-sm-3">{i18n.t("banner")}</label>
564             <div className="col-sm-9">
565               <ImageUploadForm
566                 uploadTitle={i18n.t("upload_banner")}
567                 imageSrc={this.state.saveUserSettingsForm.banner}
568                 onUpload={this.handleBannerUpload}
569                 onRemove={this.handleBannerRemove}
570               />
571             </div>
572           </div>
573           <div className="form-group row">
574             <label className="col-sm-3" htmlFor="user-language">
575               {i18n.t("interface_language")}
576             </label>
577             <div className="col-sm-9">
578               <select
579                 id="user-language"
580                 value={this.state.saveUserSettingsForm.interface_language}
581                 onChange={linkEvent(this, this.handleInterfaceLangChange)}
582                 className="custom-select w-auto"
583               >
584                 <option disabled aria-hidden="true">
585                   {i18n.t("interface_language")}
586                 </option>
587                 <option value="browser">{i18n.t("browser_default")}</option>
588                 <option disabled aria-hidden="true">
589                   â”€â”€
590                 </option>
591                 {languages
592                   .sort((a, b) => a.code.localeCompare(b.code))
593                   .map(lang => (
594                     <option key={lang.code} value={lang.code}>
595                       {lang.name}
596                     </option>
597                   ))}
598               </select>
599             </div>
600           </div>
601           <LanguageSelect
602             allLanguages={this.state.siteRes.all_languages}
603             siteLanguages={this.state.siteRes.discussion_languages}
604             selectedLanguageIds={selectedLangs}
605             multiple={true}
606             showSite
607             onChange={this.handleDiscussionLanguageChange}
608           />
609           <div className="form-group row">
610             <label className="col-sm-3" htmlFor="user-theme">
611               {i18n.t("theme")}
612             </label>
613             <div className="col-sm-9">
614               <select
615                 id="user-theme"
616                 value={this.state.saveUserSettingsForm.theme}
617                 onChange={linkEvent(this, this.handleThemeChange)}
618                 className="custom-select w-auto"
619               >
620                 <option disabled aria-hidden="true">
621                   {i18n.t("theme")}
622                 </option>
623                 <option value="browser">{i18n.t("browser_default")}</option>
624                 {this.state.themeList.map(theme => (
625                   <option key={theme} value={theme}>
626                     {theme}
627                   </option>
628                 ))}
629               </select>
630             </div>
631           </div>
632           <form className="form-group row">
633             <label className="col-sm-3">{i18n.t("type")}</label>
634             <div className="col-sm-9">
635               <ListingTypeSelect
636                 type_={
637                   Object.values(ListingType)[
638                     this.state.saveUserSettingsForm.default_listing_type ?? 1
639                   ]
640                 }
641                 showLocal={showLocal(this.isoData)}
642                 showSubscribed
643                 onChange={this.handleListingTypeChange}
644               />
645             </div>
646           </form>
647           <form className="form-group row">
648             <label className="col-sm-3">{i18n.t("sort_type")}</label>
649             <div className="col-sm-9">
650               <SortSelect
651                 sort={
652                   Object.values(SortType)[
653                     this.state.saveUserSettingsForm.default_sort_type ?? 0
654                   ]
655                 }
656                 onChange={this.handleSortTypeChange}
657               />
658             </div>
659           </form>
660           {enableNsfw(this.state.siteRes) && (
661             <div className="form-group">
662               <div className="form-check">
663                 <input
664                   className="form-check-input"
665                   id="user-show-nsfw"
666                   type="checkbox"
667                   checked={this.state.saveUserSettingsForm.show_nsfw}
668                   onChange={linkEvent(this, this.handleShowNsfwChange)}
669                 />
670                 <label className="form-check-label" htmlFor="user-show-nsfw">
671                   {i18n.t("show_nsfw")}
672                 </label>
673               </div>
674             </div>
675           )}
676           <div className="form-group">
677             <div className="form-check">
678               <input
679                 className="form-check-input"
680                 id="user-show-scores"
681                 type="checkbox"
682                 checked={this.state.saveUserSettingsForm.show_scores}
683                 onChange={linkEvent(this, this.handleShowScoresChange)}
684               />
685               <label className="form-check-label" htmlFor="user-show-scores">
686                 {i18n.t("show_scores")}
687               </label>
688             </div>
689           </div>
690           <div className="form-group">
691             <div className="form-check">
692               <input
693                 className="form-check-input"
694                 id="user-show-avatars"
695                 type="checkbox"
696                 checked={this.state.saveUserSettingsForm.show_avatars}
697                 onChange={linkEvent(this, this.handleShowAvatarsChange)}
698               />
699               <label className="form-check-label" htmlFor="user-show-avatars">
700                 {i18n.t("show_avatars")}
701               </label>
702             </div>
703           </div>
704           <div className="form-group">
705             <div className="form-check">
706               <input
707                 className="form-check-input"
708                 id="user-bot-account"
709                 type="checkbox"
710                 checked={this.state.saveUserSettingsForm.bot_account}
711                 onChange={linkEvent(this, this.handleBotAccount)}
712               />
713               <label className="form-check-label" htmlFor="user-bot-account">
714                 {i18n.t("bot_account")}
715               </label>
716             </div>
717           </div>
718           <div className="form-group">
719             <div className="form-check">
720               <input
721                 className="form-check-input"
722                 id="user-show-bot-accounts"
723                 type="checkbox"
724                 checked={this.state.saveUserSettingsForm.show_bot_accounts}
725                 onChange={linkEvent(this, this.handleShowBotAccounts)}
726               />
727               <label
728                 className="form-check-label"
729                 htmlFor="user-show-bot-accounts"
730               >
731                 {i18n.t("show_bot_accounts")}
732               </label>
733             </div>
734           </div>
735           <div className="form-group">
736             <div className="form-check">
737               <input
738                 className="form-check-input"
739                 id="user-show-read-posts"
740                 type="checkbox"
741                 checked={this.state.saveUserSettingsForm.show_read_posts}
742                 onChange={linkEvent(this, this.handleReadPosts)}
743               />
744               <label
745                 className="form-check-label"
746                 htmlFor="user-show-read-posts"
747               >
748                 {i18n.t("show_read_posts")}
749               </label>
750             </div>
751           </div>
752           <div className="form-group">
753             <div className="form-check">
754               <input
755                 className="form-check-input"
756                 id="user-show-new-post-notifs"
757                 type="checkbox"
758                 checked={this.state.saveUserSettingsForm.show_new_post_notifs}
759                 onChange={linkEvent(this, this.handleShowNewPostNotifs)}
760               />
761               <label
762                 className="form-check-label"
763                 htmlFor="user-show-new-post-notifs"
764               >
765                 {i18n.t("show_new_post_notifs")}
766               </label>
767             </div>
768           </div>
769           <div className="form-group">
770             <div className="form-check">
771               <input
772                 className="form-check-input"
773                 id="user-send-notifications-to-email"
774                 type="checkbox"
775                 disabled={!this.state.saveUserSettingsForm.email}
776                 checked={
777                   this.state.saveUserSettingsForm.send_notifications_to_email
778                 }
779                 onChange={linkEvent(
780                   this,
781                   this.handleSendNotificationsToEmailChange
782                 )}
783               />
784               <label
785                 className="form-check-label"
786                 htmlFor="user-send-notifications-to-email"
787               >
788                 {i18n.t("send_notifications_to_email")}
789               </label>
790             </div>
791           </div>
792           <div className="form-group">
793             <button type="submit" className="btn btn-block btn-secondary mr-4">
794               {this.state.saveUserSettingsLoading ? (
795                 <Spinner />
796               ) : (
797                 capitalizeFirstLetter(i18n.t("save"))
798               )}
799             </button>
800           </div>
801           <hr />
802           <div className="form-group">
803             <button
804               className="btn btn-block btn-danger"
805               onClick={linkEvent(
806                 this,
807                 this.handleDeleteAccountShowConfirmToggle
808               )}
809             >
810               {i18n.t("delete_account")}
811             </button>
812             {this.state.deleteAccountShowConfirm && (
813               <>
814                 <div className="my-2 alert alert-danger" role="alert">
815                   {i18n.t("delete_account_confirm")}
816                 </div>
817                 <input
818                   type="password"
819                   value={this.state.deleteAccountForm.password}
820                   autoComplete="new-password"
821                   maxLength={60}
822                   onInput={linkEvent(
823                     this,
824                     this.handleDeleteAccountPasswordChange
825                   )}
826                   className="form-control my-2"
827                 />
828                 <button
829                   className="btn btn-danger mr-4"
830                   disabled={!this.state.deleteAccountForm.password}
831                   onClick={linkEvent(this, this.handleDeleteAccount)}
832                 >
833                   {this.state.deleteAccountLoading ? (
834                     <Spinner />
835                   ) : (
836                     capitalizeFirstLetter(i18n.t("delete"))
837                   )}
838                 </button>
839                 <button
840                   className="btn btn-secondary"
841                   onClick={linkEvent(
842                     this,
843                     this.handleDeleteAccountShowConfirmToggle
844                   )}
845                 >
846                   {i18n.t("cancel")}
847                 </button>
848               </>
849             )}
850           </div>
851         </form>
852       </>
853     );
854   }
855
856   setupBlockPersonChoices() {
857     if (isBrowser()) {
858       let selectId: any = document.getElementById("block-person-filter");
859       if (selectId) {
860         this.blockPersonChoices = new Choices(selectId, choicesConfig);
861         this.blockPersonChoices.passedElement.element.addEventListener(
862           "choice",
863           (e: any) => {
864             this.handleBlockPerson(Number(e.detail.choice.value));
865           },
866           false
867         );
868         this.blockPersonChoices.passedElement.element.addEventListener(
869           "search",
870           debounce(async (e: any) => {
871             try {
872               let persons = (await fetchUsers(e.detail.value)).users;
873               let choices = persons.map(pvs => personToChoice(pvs));
874               this.blockPersonChoices.setChoices(
875                 choices,
876                 "value",
877                 "label",
878                 true
879               );
880             } catch (err) {
881               console.error(err);
882             }
883           }),
884           false
885         );
886       }
887     }
888   }
889
890   setupBlockCommunityChoices() {
891     if (isBrowser()) {
892       let selectId: any = document.getElementById("block-community-filter");
893       if (selectId) {
894         this.blockCommunityChoices = new Choices(selectId, choicesConfig);
895         this.blockCommunityChoices.passedElement.element.addEventListener(
896           "choice",
897           (e: any) => {
898             this.handleBlockCommunity(Number(e.detail.choice.value));
899           },
900           false
901         );
902         this.blockCommunityChoices.passedElement.element.addEventListener(
903           "search",
904           debounce(async (e: any) => {
905             try {
906               let communities = (await fetchCommunities(e.detail.value))
907                 .communities;
908               let choices = communities.map(cv => communityToChoice(cv));
909               this.blockCommunityChoices.setChoices(
910                 choices,
911                 "value",
912                 "label",
913                 true
914               );
915             } catch (err) {
916               console.log(err);
917             }
918           }),
919           false
920         );
921       }
922     }
923   }
924
925   handleBlockPerson(personId: number) {
926     let auth = myAuth();
927     if (auth && personId != 0) {
928       let blockUserForm: BlockPerson = {
929         person_id: personId,
930         block: true,
931         auth,
932       };
933       WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
934     }
935   }
936
937   handleUnblockPerson(i: { ctx: Settings; recipientId: number }) {
938     let auth = myAuth();
939     if (auth) {
940       let blockUserForm: BlockPerson = {
941         person_id: i.recipientId,
942         block: false,
943         auth,
944       };
945       WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
946     }
947   }
948
949   handleBlockCommunity(community_id: number) {
950     let auth = myAuth();
951     if (auth && community_id != 0) {
952       let blockCommunityForm: BlockCommunity = {
953         community_id,
954         block: true,
955         auth,
956       };
957       WebSocketService.Instance.send(
958         wsClient.blockCommunity(blockCommunityForm)
959       );
960     }
961   }
962
963   handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
964     let auth = myAuth();
965     if (auth) {
966       let blockCommunityForm: BlockCommunity = {
967         community_id: i.communityId,
968         block: false,
969         auth,
970       };
971       WebSocketService.Instance.send(
972         wsClient.blockCommunity(blockCommunityForm)
973       );
974     }
975   }
976
977   handleShowNsfwChange(i: Settings, event: any) {
978     i.state.saveUserSettingsForm.show_nsfw = event.target.checked;
979     i.setState(i.state);
980   }
981
982   handleShowAvatarsChange(i: Settings, event: any) {
983     i.state.saveUserSettingsForm.show_avatars = event.target.checked;
984     let mui = UserService.Instance.myUserInfo;
985     if (mui) {
986       mui.local_user_view.local_user.show_avatars = event.target.checked;
987     }
988     i.setState(i.state);
989   }
990
991   handleBotAccount(i: Settings, event: any) {
992     i.state.saveUserSettingsForm.bot_account = event.target.checked;
993     i.setState(i.state);
994   }
995
996   handleShowBotAccounts(i: Settings, event: any) {
997     i.state.saveUserSettingsForm.show_bot_accounts = event.target.checked;
998     i.setState(i.state);
999   }
1000
1001   handleReadPosts(i: Settings, event: any) {
1002     i.state.saveUserSettingsForm.show_read_posts = event.target.checked;
1003     i.setState(i.state);
1004   }
1005
1006   handleShowNewPostNotifs(i: Settings, event: any) {
1007     i.state.saveUserSettingsForm.show_new_post_notifs = event.target.checked;
1008     i.setState(i.state);
1009   }
1010
1011   handleShowScoresChange(i: Settings, event: any) {
1012     i.state.saveUserSettingsForm.show_scores = event.target.checked;
1013     let mui = UserService.Instance.myUserInfo;
1014     if (mui) {
1015       mui.local_user_view.local_user.show_scores = event.target.checked;
1016     }
1017     i.setState(i.state);
1018   }
1019
1020   handleSendNotificationsToEmailChange(i: Settings, event: any) {
1021     i.state.saveUserSettingsForm.send_notifications_to_email =
1022       event.target.checked;
1023     i.setState(i.state);
1024   }
1025
1026   handleThemeChange(i: Settings, event: any) {
1027     i.state.saveUserSettingsForm.theme = event.target.value;
1028     setTheme(event.target.value, true);
1029     i.setState(i.state);
1030   }
1031
1032   handleInterfaceLangChange(i: Settings, event: any) {
1033     i.state.saveUserSettingsForm.interface_language = event.target.value;
1034     i18n.changeLanguage(
1035       getLanguages(i.state.saveUserSettingsForm.interface_language).at(0)
1036     );
1037     i.setState(i.state);
1038   }
1039
1040   handleDiscussionLanguageChange(val: number[]) {
1041     this.setState(
1042       s => ((s.saveUserSettingsForm.discussion_languages = val), s)
1043     );
1044   }
1045
1046   handleSortTypeChange(val: SortType) {
1047     this.setState(
1048       s => (
1049         (s.saveUserSettingsForm.default_sort_type =
1050           Object.keys(SortType).indexOf(val)),
1051         s
1052       )
1053     );
1054   }
1055
1056   handleListingTypeChange(val: ListingType) {
1057     this.setState(
1058       s => (
1059         (s.saveUserSettingsForm.default_listing_type =
1060           Object.keys(ListingType).indexOf(val)),
1061         s
1062       )
1063     );
1064   }
1065
1066   handleEmailChange(i: Settings, event: any) {
1067     i.state.saveUserSettingsForm.email = event.target.value;
1068     i.setState(i.state);
1069   }
1070
1071   handleBioChange(val: string) {
1072     this.setState(s => ((s.saveUserSettingsForm.bio = val), s));
1073   }
1074
1075   handleAvatarUpload(url: string) {
1076     this.setState(s => ((s.saveUserSettingsForm.avatar = url), s));
1077   }
1078
1079   handleAvatarRemove() {
1080     this.setState(s => ((s.saveUserSettingsForm.avatar = ""), s));
1081   }
1082
1083   handleBannerUpload(url: string) {
1084     this.setState(s => ((s.saveUserSettingsForm.banner = url), s));
1085   }
1086
1087   handleBannerRemove() {
1088     this.setState(s => ((s.saveUserSettingsForm.banner = ""), s));
1089   }
1090
1091   handleDisplayNameChange(i: Settings, event: any) {
1092     i.state.saveUserSettingsForm.display_name = event.target.value;
1093     i.setState(i.state);
1094   }
1095
1096   handleMatrixUserIdChange(i: Settings, event: any) {
1097     i.state.saveUserSettingsForm.matrix_user_id = event.target.value;
1098     i.setState(i.state);
1099   }
1100
1101   handleNewPasswordChange(i: Settings, event: any) {
1102     i.state.changePasswordForm.new_password = event.target.value;
1103     if (i.state.changePasswordForm.new_password == "") {
1104       i.state.changePasswordForm.new_password = undefined;
1105     }
1106     i.setState(i.state);
1107   }
1108
1109   handleNewPasswordVerifyChange(i: Settings, event: any) {
1110     i.state.changePasswordForm.new_password_verify = event.target.value;
1111     if (i.state.changePasswordForm.new_password_verify == "") {
1112       i.state.changePasswordForm.new_password_verify = undefined;
1113     }
1114     i.setState(i.state);
1115   }
1116
1117   handleOldPasswordChange(i: Settings, event: any) {
1118     i.state.changePasswordForm.old_password = event.target.value;
1119     if (i.state.changePasswordForm.old_password == "") {
1120       i.state.changePasswordForm.old_password = undefined;
1121     }
1122     i.setState(i.state);
1123   }
1124
1125   handleSaveSettingsSubmit(i: Settings, event: any) {
1126     event.preventDefault();
1127     i.setState({ saveUserSettingsLoading: true });
1128     let auth = myAuth();
1129     if (auth) {
1130       let form: SaveUserSettings = { ...i.state.saveUserSettingsForm, auth };
1131       WebSocketService.Instance.send(wsClient.saveUserSettings(form));
1132     }
1133   }
1134
1135   handleChangePasswordSubmit(i: Settings, event: any) {
1136     event.preventDefault();
1137     i.setState({ changePasswordLoading: true });
1138     let auth = myAuth();
1139     let pForm = i.state.changePasswordForm;
1140     let new_password = pForm.new_password;
1141     let new_password_verify = pForm.new_password_verify;
1142     let old_password = pForm.old_password;
1143     if (auth && new_password && old_password && new_password_verify) {
1144       let form: ChangePassword = {
1145         new_password,
1146         new_password_verify,
1147         old_password,
1148         auth,
1149       };
1150
1151       WebSocketService.Instance.send(wsClient.changePassword(form));
1152     }
1153   }
1154
1155   handleDeleteAccountShowConfirmToggle(i: Settings, event: any) {
1156     event.preventDefault();
1157     i.setState({ deleteAccountShowConfirm: !i.state.deleteAccountShowConfirm });
1158   }
1159
1160   handleDeleteAccountPasswordChange(i: Settings, event: any) {
1161     i.state.deleteAccountForm.password = event.target.value;
1162     i.setState(i.state);
1163   }
1164
1165   handleDeleteAccount(i: Settings, event: any) {
1166     event.preventDefault();
1167     i.setState({ deleteAccountLoading: true });
1168     let auth = myAuth();
1169     let password = i.state.deleteAccountForm.password;
1170     if (auth && password) {
1171       let form: DeleteAccount = {
1172         password,
1173         auth,
1174       };
1175       WebSocketService.Instance.send(wsClient.deleteAccount(form));
1176     }
1177   }
1178
1179   handleSwitchTab(i: { ctx: Settings; tab: string }) {
1180     i.ctx.setState({ currentTab: i.tab });
1181
1182     if (i.ctx.state.currentTab == "blocks") {
1183       i.ctx.setupBlockPersonChoices();
1184       i.ctx.setupBlockCommunityChoices();
1185     }
1186   }
1187
1188   parseMessage(msg: any) {
1189     let op = wsUserOp(msg);
1190     console.log(msg);
1191     if (msg.error) {
1192       this.setState({
1193         saveUserSettingsLoading: false,
1194         changePasswordLoading: false,
1195         deleteAccountLoading: false,
1196       });
1197       toast(i18n.t(msg.error), "danger");
1198       return;
1199     } else if (op == UserOperation.SaveUserSettings) {
1200       let data = wsJsonToRes<LoginResponse>(msg);
1201       UserService.Instance.login(data);
1202       location.reload();
1203       this.setState({ saveUserSettingsLoading: false });
1204       toast(i18n.t("saved"));
1205       window.scrollTo(0, 0);
1206     } else if (op == UserOperation.ChangePassword) {
1207       let data = wsJsonToRes<LoginResponse>(msg);
1208       UserService.Instance.login(data);
1209       this.setState({ changePasswordLoading: false });
1210       window.scrollTo(0, 0);
1211       toast(i18n.t("password_changed"));
1212     } else if (op == UserOperation.DeleteAccount) {
1213       this.setState({
1214         deleteAccountLoading: false,
1215         deleteAccountShowConfirm: false,
1216       });
1217       UserService.Instance.logout();
1218       window.location.href = "/";
1219     } else if (op == UserOperation.BlockPerson) {
1220       let data = wsJsonToRes<BlockPersonResponse>(msg);
1221       updatePersonBlock(data);
1222       let mui = UserService.Instance.myUserInfo;
1223       if (mui) {
1224         this.setState({ personBlocks: mui.person_blocks });
1225       }
1226     } else if (op == UserOperation.BlockCommunity) {
1227       let data = wsJsonToRes<BlockCommunityResponse>(msg);
1228       updateCommunityBlock(data);
1229       let mui = UserService.Instance.myUserInfo;
1230       if (mui) {
1231         this.setState({ communityBlocks: mui.community_blocks });
1232       }
1233     }
1234   }
1235 }