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